Compare commits

..

33 Commits

Author SHA1 Message Date
Romain Vimont
f56be59a0c Document remote ADB server in README
Refs #2807 <https://github.com/Genymobile/scrcpy/pull/2807>
2021-11-19 21:11:56 +01:00
Romain Vimont
64400c8740 Force adb forward if tunnel host/port is provided
Tunnel host and port are only meaningful in "adb forward" mode.

They indicate where the client must connect to communicate with the
server. In "adb reverse" mode, the client _listens_, it does not
_connect_.

Refs #2807 <https://github.com/Genymobile/scrcpy/pull/2807>
2021-11-19 21:07:22 +01:00
RipleyTom
46f7cd31b9 Add options to configure tunnel host and port
In "adb forward" mode, by default, scrcpy connects to localhost:PORT,
where PORT is the local port passed to "adb forward". This assumes that
the tunnel is established on the local host with a local adb server
(which is the common case).

For advanced usage, add --tunnel-host and --tunnel-port to force the
connection to a different destination.

Fixes #2801 <https://github.com/Genymobile/scrcpy/issues/2801>
PR #2807 <https://github.com/Genymobile/scrcpy/pull/2807>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-11-19 21:05:11 +01:00
RipleyTom
d290b44248 Add function to parse IPv4 addresses
PR #2807 <https://github.com/Genymobile/scrcpy/pull/2807>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-11-19 21:04:52 +01:00
Romain Vimont
2d6a96776c Improve SSH tunnel documentation in README 2021-11-19 21:03:29 +01:00
Romain Vimont
6da6d905c2 Print scrcpy header first
Inconditionnally print the scrcpy version first, without using LOGI().

The log level is configured only after parsing the command line
parameters (it may be changed via -V), and command line parsing logs
should not appear before the scrcpy version.
2021-11-19 09:45:02 +01:00
Romain Vimont
b25404ee4b Print help to stdout
The output of -h/--help was printed on stderr, although it is not an
error.

Printing on stdout allows to pipe the result directly:

    scrcpy --help | less

Instead of (in bash):

    scrcpy --help |& less
2021-11-19 08:18:13 +01:00
Romain Vimont
4cfc1cd70a Assert that long options are correctly set 2021-11-19 08:06:23 +01:00
Romain Vimont
2fc80eae2d Simplify adb_tunnel
With the new adb functions, the static adb_tunnel functions become
unnecessary.
2021-11-19 07:25:03 +01:00
Romain Vimont
3fdbd994e0 Privatize low-level adb functions
Only expose the interruptible user-friendly API.
2021-11-19 07:25:03 +01:00
Romain Vimont
ce225f019a Use new user-friendly adb API
Replace the adb_exec_*() calls by the new adb functions to simplify.
2021-11-18 22:11:19 +01:00
Romain Vimont
b7559744a7 Expose new user-friendly adb functions
Expose interruptible adb functions which return the expected result
directly, without exposing the process (sc_pid) to the caller.
2021-11-18 22:08:15 +01:00
Romain Vimont
afb5a5e80f Rename adb functions to adb_exec_*
This paves the way to replace them by more user-friendly functions that
will call them internally.
2021-11-18 21:47:17 +01:00
Romain Vimont
84334cf7db Use sc_intr in file_handler
Replace manual interruption handling by the recent sc_intr mechanism.
2021-11-18 21:33:25 +01:00
Romain Vimont
0ba2686e1d Simplify file_handler
Call the target functions directly.
2021-11-18 19:46:47 +01:00
Romain Vimont
55648d4d64 Improve file_handler readability
Use local variables as short name aliases.
2021-11-18 19:46:40 +01:00
Romain Vimont
13fd693b50 Simplify adb_execute_p()
Only pass the stdout pipe as parameter, scrcpy never writes to stdin or
reads from stderr of an adb process.
2021-11-18 19:43:10 +01:00
Romain Vimont
0426ae885c Make "adb get-serialno" interruptible
All process executions must be interruptible, so that Ctrl+c reacts
immediately.
2021-11-18 19:41:41 +01:00
Romain Vimont
ea454e9cee Add interruptible function to read from pipe
This will avoid to block Ctrl+c if the process the pipe is read from
takes too much time.
2021-11-18 19:39:11 +01:00
Romain Vimont
cb65531533 Simplify sc_str_truncate()
Use strcspn() to get the prefix length directly.
2021-11-18 19:37:53 +01:00
Romain Vimont
9619ade706 Generalize string trunctation util function
Add an additional argument to let the client pass the possible end
chars.
2021-11-18 18:48:16 +01:00
Romain Vimont
f2781a8b6d Expose util function to truncate first line
Move the local implementation from adb functions to the string util
functions.
2021-11-18 18:48:16 +01:00
Romain Vimont
443cb14d6e Assume non-NULL serial in file_handler
The previous commit guarantees to always initialize the serial, so the
file_handle may assume it is never NULL.
2021-11-18 18:48:11 +01:00
Romain Vimont
b30c3a429f Always retrieve device serial
This allows to execute all adb commands with the specific -s parameter,
even if it is not provided by the user.

In practice, calling adb without -s works if there is exactly one device
connected. But some adb commands (for example "adb push" on drag & drop)
could be executed after another device is connected, so the actual
device serial must be known.
2021-11-18 18:32:15 +01:00
Romain Vimont
632bd5697b Add missing error handling
If "adb get-serialno" fails, attempting to read from the uninitialized
pipe is incorrect.
2021-11-18 18:32:15 +01:00
Romain Vimont
de50846905 Close process on check success
The interruptible version of the function to check process success
(sc_process_check_success_intr()) did not accept a close parameter to
avoid a race condition. But as the result, the processes were not closed
at all.

Add a close parameter, and close the process separately to avoid the
race condition.
2021-11-18 18:31:36 +01:00
Romain Vimont
ee93d2aac1 Configure init and cleanup asynchronously
Accessing the settings (like --show-touches) on start should not delay
screen mirroring.

PR #2802 <https://github.com/Genymobile/scrcpy/pull/2802>
2021-11-18 08:37:32 +01:00
Romain Vimont
c29a0bf675 Do not quit on cleanup configuration failure
Cleanup is used for some options like --show-touches to restore the
state on exit.

If the configuration fails, do not crash the whole process. Just log an
error.

PR #2802 <https://github.com/Genymobile/scrcpy/pull/2802>
2021-11-18 08:37:28 +01:00
Romain Vimont
411bb0d18e Move init and cleanup to a separate method
PR #2802 <https://github.com/Genymobile/scrcpy/pull/2802>
2021-11-18 08:37:26 +01:00
Romain Vimont
cc0902b13c Read/write settings via command on Android >= 12
Before Android 8, executing the "settings" command from a shell was
very slow (~1 second), because it spawned a new app_process to execute
Java code. Therefore, to access settings without performance issues,
scrcpy used private APIs to read from and write to settings.

However, since Android 12, this is not possible anymore, due to
permissions changes.

To make it work again, execute the "settings" command on Android 12 (or
on previous version if the other method failed). This method is faster
than before Android 8 (~100ms).

Fixes #2671 <https://github.com/Genymobile/scrcpy/issues/2671>
Fixes #2788 <https://github.com/Genymobile/scrcpy/issues/2788>
PR #2802 <https://github.com/Genymobile/scrcpy/pull/2802>
2021-11-18 08:37:22 +01:00
Romain Vimont
48b572c272 Add throwable parameter to Log.w()
When an exception occurs, we might want to log a warning instead of an
error.

PR #2802 <https://github.com/Genymobile/scrcpy/pull/2802>
2021-11-18 08:37:18 +01:00
Romain Vimont
94feae71f2 Report settings errors via Exceptions
Settings read/write errors were silently ignored. Report them via a
SettingsException so that the caller can handle them.

This allows to log a proper error message, and will also allow to
fallback to a different settings method in case of failure.

PR #2802 <https://github.com/Genymobile/scrcpy/pull/2802>
2021-11-18 08:37:02 +01:00
Romain Vimont
67170437f1 Wrap settings management into a Settings class
Until now, the code that needed to read/write the Android settings had
to explicitly open and close a ContentProvider.

Wrap these details into a Settings class.

This paves the way to provide an alternative implementation of settings
read/write for Android >= 12.

PR #2802 <https://github.com/Genymobile/scrcpy/pull/2802>
2021-11-18 08:36:48 +01:00
21 changed files with 368 additions and 194 deletions

View File

@@ -412,21 +412,66 @@ autoadb scrcpy -s '{}'
[AutoAdb]: https://github.com/rom1v/autoadb
#### SSH tunnel
#### Tunnels
To connect to a remote device, it is possible to connect a local `adb` client to
a remote `adb` server (provided they use the same version of the _adb_
protocol):
protocol).
##### Remote ADB server
To connect to a remote ADB server, make the server listen on all interfaces:
```bash
adb kill-server # kill the local adb server on 5037
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
adb kill-server
adb -a nodaemon server start
# keep this open
```
From another terminal:
**Warning: all communications between clients and ADB server are unencrypted.**
Suppose that this server is accessible at 192.168.1.2. Then, from another
terminal, run scrcpy:
```bash
export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037
scrcpy --tunnel-host=192.168.1.2
```
By default, scrcpy uses the local port used for `adb forward` tunnel
establishment (typically `27183`, see `--port`). It is also possible to force a
different tunnel port (it may be useful in more complex situations, when more
redirections are involved):
```
scrcpy --tunnel-port=1234
```
##### SSH tunnel
To communicate with a remote ADB server securely, it is preferable to use a SSH
tunnel.
First, make sure the ADB server is running on the remote computer:
```bash
adb start-server
```
Then, establish a SSH tunnel:
```bash
# local 5038 --> remote 5037
# local 27183 <-- remote 27183
ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer
# keep this open
```
From another terminal, run scrcpy:
```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy
```
@@ -434,14 +479,16 @@ To avoid enabling remote port forwarding, you could force a forward connection
instead (notice the `-L` instead of `-R`):
```bash
adb kill-server # kill the local adb server on 5037
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# local 5038 --> remote 5037
# local 27183 --> remote 27183
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
# keep this open
```
From another terminal:
From another terminal, run scrcpy:
```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy --force-adb-forward
```

View File

@@ -199,6 +199,7 @@ if get_option('buildtype') == 'debug'
'tests/test_cli.c',
'src/cli.c',
'src/options.c',
'src/util/net.c',
'src/util/str.c',
'src/util/strbuf.c',
'src/util/term.c',

View File

@@ -203,6 +203,18 @@ Enable "show touches" on start, restore the initial value on exit.
It only shows physical touches (not clicks from scrcpy).
.TP
.BI "\-\-tunnel\-host " ip
Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
Default is localhost.
.TP
.BI "\-\-tunnel\-port " port
Set the TCP port of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
Default is 0 (not forced): the local port used for establishing the tunnel will be used.
.TP
.BI "\-\-v4l2-sink " /dev/videoN
Output to v4l2loopback device.

View File

@@ -7,6 +7,7 @@
#include "util/file.h"
#include "util/log.h"
#include "util/process_intr.h"
#include "util/str.h"
static const char *adb_command;
@@ -109,7 +110,7 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) {
free(buf);
}
sc_pid
static sc_pid
adb_execute_p(const char *serial, const char *const adb_cmd[],
size_t len, sc_pipe *pout) {
int i;
@@ -147,9 +148,9 @@ adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
return adb_execute_p(serial, adb_cmd, len, NULL);
}
sc_pid
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name) {
static sc_pid
adb_exec_forward(const char *serial, uint16_t local_port,
const char *device_socket_name) {
char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port);
@@ -158,17 +159,17 @@ adb_forward(const char *serial, uint16_t local_port,
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
sc_pid
adb_forward_remove(const char *serial, uint16_t local_port) {
static sc_pid
adb_exec_forward_remove(const char *serial, uint16_t local_port) {
char local[4 + 5 + 1]; // tcp:PORT
sprintf(local, "tcp:%" PRIu16, local_port);
const char *const adb_cmd[] = {"forward", "--remove", local};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
sc_pid
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port) {
static sc_pid
adb_exec_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port) {
char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port);
@@ -177,16 +178,16 @@ adb_reverse(const char *serial, const char *device_socket_name,
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
sc_pid
adb_reverse_remove(const char *serial, const char *device_socket_name) {
static sc_pid
adb_exec_reverse_remove(const char *serial, const char *device_socket_name) {
char remote[108 + 14 + 1]; // localabstract:NAME
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
const char *const adb_cmd[] = {"reverse", "--remove", remote};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
sc_pid
adb_push(const char *serial, const char *local, const char *remote) {
static sc_pid
adb_exec_push(const char *serial, const char *local, const char *remote) {
#ifdef __WINDOWS__
// Windows will parse the string, so the paths must be quoted
// (see sys/win/command.c)
@@ -212,8 +213,8 @@ adb_push(const char *serial, const char *local, const char *remote) {
return pid;
}
sc_pid
adb_install(const char *serial, const char *local) {
static sc_pid
adb_exec_install(const char *serial, const char *local) {
#ifdef __WINDOWS__
// Windows will parse the string, so the local name must be quoted
// (see sys/win/command.c)
@@ -233,8 +234,75 @@ adb_install(const char *serial, const char *local) {
return pid;
}
sc_pid
adb_get_serialno(sc_pipe *pout) {
static sc_pid
adb_exec_get_serialno(sc_pipe *pout) {
const char *const adb_cmd[] = {"get-serialno"};
return adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), pout);
}
bool
adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name) {
sc_pid pid = adb_exec_forward(serial, local_port, device_socket_name);
return sc_process_check_success_intr(intr, pid, "adb forward", true);
}
bool
adb_forward_remove(struct sc_intr *intr, const char *serial,
uint16_t local_port) {
sc_pid pid = adb_exec_forward_remove(serial, local_port);
return sc_process_check_success_intr(intr, pid, "adb forward --remove",
true);
}
bool
adb_reverse(struct sc_intr *intr, const char *serial,
const char *device_socket_name, uint16_t local_port) {
sc_pid pid = adb_exec_reverse(serial, device_socket_name, local_port);
return sc_process_check_success_intr(intr, pid, "adb reverse", true);
}
bool
adb_reverse_remove(struct sc_intr *intr, const char *serial,
const char *device_socket_name) {
sc_pid pid = adb_exec_reverse_remove(serial, device_socket_name);
return sc_process_check_success_intr(intr, pid, "adb reverse --remove",
true);
}
bool
adb_push(struct sc_intr *intr, const char *serial, const char *local,
const char *remote) {
sc_pid pid = adb_exec_push(serial, local, remote);
return sc_process_check_success_intr(intr, pid, "adb push", true);
}
bool
adb_install(struct sc_intr *intr, const char *serial, const char *local) {
sc_pid pid = adb_exec_install(serial, local);
return sc_process_check_success_intr(intr, pid, "adb install", true);
}
char *
adb_get_serialno(struct sc_intr *intr) {
sc_pipe pout;
sc_pid pid = adb_exec_get_serialno(&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 =
sc_process_check_success_intr(intr, pid, "adb get-serialno", true);
if (!ok) {
return NULL;
}
sc_str_truncate(buf, r, " \r\n");
return strdup(buf);
}

View File

@@ -6,37 +6,40 @@
#include <stdbool.h>
#include <inttypes.h>
#include "util/process.h"
#include "util/intr.h"
sc_pid
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
sc_pid
adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len,
sc_pipe *pout);
sc_pid
adb_forward(const char *serial, uint16_t local_port,
bool
adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name);
sc_pid
adb_forward_remove(const char *serial, uint16_t local_port);
bool
adb_forward_remove(struct sc_intr *intr, const char *serial,
uint16_t local_port);
sc_pid
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port);
bool
adb_reverse(struct sc_intr *intr, const char *serial,
const char *device_socket_name, uint16_t local_port);
sc_pid
adb_reverse_remove(const char *serial, const char *device_socket_name);
bool
adb_reverse_remove(struct sc_intr *intr, const char *serial,
const char *device_socket_name);
sc_pid
adb_push(const char *serial, const char *local, const char *remote);
bool
adb_push(struct sc_intr *intr, const char *serial, const char *local,
const char *remote);
sc_pid
adb_install(const char *serial, const char *local);
bool
adb_install(struct sc_intr *intr, const char *serial, const char *local);
// Execute "adb get-serialno" and give a pipe to get the result
sc_pid
adb_get_serialno(sc_pipe *pout);
/**
* 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);
#endif

View File

@@ -9,33 +9,6 @@
#define SC_SOCKET_NAME "scrcpy"
static bool
enable_tunnel_reverse(struct sc_intr *intr, const char *serial,
uint16_t local_port) {
sc_pid pid = adb_reverse(serial, SC_SOCKET_NAME, local_port);
return sc_process_check_success_intr(intr, pid, "adb reverse");
}
static bool
disable_tunnel_reverse(struct sc_intr *intr, const char *serial) {
sc_pid pid = adb_reverse_remove(serial, SC_SOCKET_NAME);
return sc_process_check_success_intr(intr, pid, "adb reverse --remove");
}
static bool
enable_tunnel_forward(struct sc_intr *intr, const char *serial,
uint16_t local_port) {
sc_pid pid = adb_forward(serial, local_port, SC_SOCKET_NAME);
return sc_process_check_success_intr(intr, pid, "adb forward");
}
static bool
disable_tunnel_forward(struct sc_intr *intr, const char *serial,
uint16_t local_port) {
sc_pid pid = adb_forward_remove(serial, local_port);
return sc_process_check_success_intr(intr, pid, "adb forward --remove");
}
static bool
listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) {
return net_listen_intr(intr, socket, IPV4_LOCALHOST, port, 1);
@@ -47,7 +20,7 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
struct sc_port_range port_range) {
uint16_t port = port_range.first;
for (;;) {
if (!enable_tunnel_reverse(intr, serial, port)) {
if (!adb_reverse(intr, serial, SC_SOCKET_NAME, port)) {
// the command itself failed, it will fail on any port
return false;
}
@@ -78,7 +51,7 @@ enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
}
// failure, disable tunnel and try another port
if (!disable_tunnel_reverse(intr, serial)) {
if (!adb_reverse_remove(intr, serial, SC_SOCKET_NAME)) {
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
}
@@ -108,7 +81,7 @@ enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel,
uint16_t port = port_range.first;
for (;;) {
if (enable_tunnel_forward(intr, serial, port)) {
if (adb_forward(intr, serial, port, SC_SOCKET_NAME)) {
// success
tunnel->local_port = port;
tunnel->enabled = true;
@@ -173,9 +146,9 @@ sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
bool ret;
if (tunnel->forward) {
ret = disable_tunnel_forward(intr, serial, tunnel->local_port);
ret = adb_forward_remove(intr, serial, tunnel->local_port);
} else {
ret = disable_tunnel_reverse(intr, serial);
ret = adb_reverse_remove(intr, serial, SC_SOCKET_NAME);
assert(tunnel->server_socket != SC_SOCKET_NONE);
if (!net_close(tunnel->server_socket)) {

View File

@@ -9,6 +9,7 @@
#include "options.h"
#include "util/log.h"
#include "util/net.h"
#include "util/str.h"
#include "util/strbuf.h"
#include "util/term.h"
@@ -46,6 +47,8 @@
#define OPT_V4L2_SINK 1027
#define OPT_DISPLAY_BUFFER 1028
#define OPT_V4L2_BUFFER 1029
#define OPT_TUNNEL_HOST 1030
#define OPT_TUNNEL_PORT 1031
struct sc_option {
char shortopt;
@@ -330,6 +333,25 @@ static const struct sc_option options[] = {
"on exit.\n"
"It only shows physical touches (not clicks from scrcpy).",
},
{
.longopt_id = OPT_TUNNEL_HOST,
.longopt = "tunnel-host",
.argdesc = "ip",
.text = "Set the IP address of the adb tunnel to reach the scrcpy "
"server. This option automatically enables "
"--force-adb-forward.\n"
"Default is localhost.",
},
{
.longopt_id = OPT_TUNNEL_PORT,
.longopt = "tunnel-port",
.argdesc = "port",
.text = "Set the TCP port of the adb tunnel to reach the scrcpy "
"server. This option automatically enables "
"--force-adb-forward.\n"
"Default is 0 (not forced): the local port used for "
"establishing the tunnel will be used.",
},
#ifdef HAVE_V4L2
{
.longopt_id = OPT_V4L2_SINK,
@@ -569,6 +591,10 @@ sc_getopt_adapter_create_longopts(void) {
size_t out_idx = 0;
for (size_t i = 0; i < ARRAY_LEN(options); ++i) {
const struct sc_option *in = &options[i];
// If longopt_id is set, then longopt must be set
assert(!in->longopt_id || in->longopt);
if (!in->longopt) {
// The longopts array must only contain long options
continue;
@@ -671,12 +697,12 @@ print_option_usage_header(const struct sc_option *opt) {
}
}
fprintf(stderr, "\n %s\n", buf.s);
printf("\n %s\n", buf.s);
free(buf.s);
return;
error:
fprintf(stderr, "<ERROR>\n");
printf("<ERROR>\n");
}
static void
@@ -692,11 +718,11 @@ print_option_usage(const struct sc_option *opt, unsigned cols) {
char *text = sc_str_wrap_lines(opt->text, cols, 8);
if (!text) {
fprintf(stderr, "<ERROR>\n");
printf("<ERROR>\n");
return;
}
fprintf(stderr, "%s\n", text);
printf("%s\n", text);
free(text);
}
@@ -707,11 +733,11 @@ print_shortcuts_intro(unsigned cols) {
"(left) Alt or (left) Super, but it can be configured by "
"--shortcut-mod (see above).", cols, 4);
if (!intro) {
fprintf(stderr, "<ERROR>\n");
printf("<ERROR>\n");
return;
}
fprintf(stderr, "%s\n", intro);
printf("%s\n", intro);
free(intro);
}
@@ -721,21 +747,21 @@ print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) {
assert(shortcut->shortcuts[0]); // At least one shortcut
assert(shortcut->text);
fprintf(stderr, "\n");
printf("\n");
unsigned i = 0;
while (shortcut->shortcuts[i]) {
fprintf(stderr, " %s\n", shortcut->shortcuts[i]);
printf(" %s\n", shortcut->shortcuts[i]);
++i;
};
char *text = sc_str_wrap_lines(shortcut->text, cols, 8);
if (!text) {
fprintf(stderr, "<ERROR>\n");
printf("<ERROR>\n");
return;
}
fprintf(stderr, "%s\n", text);
printf("%s\n", text);
free(text);
}
@@ -759,14 +785,14 @@ scrcpy_print_usage(const char *arg0) {
}
}
fprintf(stderr, "Usage: %s [options]\n\n"
"Options:\n", arg0);
printf("Usage: %s [options]\n\n"
"Options:\n", arg0);
for (size_t i = 0; i < ARRAY_LEN(options); ++i) {
print_option_usage(&options[i], cols);
}
// Print shortcuts section
fprintf(stderr, "\nShortcuts:\n\n");
printf("\nShortcuts:\n\n");
print_shortcuts_intro(cols);
for (size_t i = 0; i < ARRAY_LEN(shortcuts); ++i) {
print_shortcut(&shortcuts[i], cols);
@@ -1123,6 +1149,21 @@ parse_record_format(const char *optarg, enum sc_record_format *format) {
return false;
}
static bool
parse_ip(const char *optarg, uint32_t *ipv4) {
return net_parse_ipv4(optarg, ipv4);
}
static bool
parse_port(const char *optarg, uint16_t *port) {
long value;
if (!parse_integer_arg(optarg, &value, false, 0, 0xFFFF, "port")) {
return false;
}
*port = (uint16_t) value;
return true;
}
static enum sc_record_format
guess_record_format(const char *filename) {
size_t len = strlen(filename);
@@ -1195,6 +1236,16 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false;
}
break;
case OPT_TUNNEL_HOST:
if (!parse_ip(optarg, &opts->tunnel_host)) {
return false;
}
break;
case OPT_TUNNEL_PORT:
if (!parse_port(optarg, &opts->tunnel_port)) {
return false;
}
break;
case 'n':
opts->control = false;
break;
@@ -1354,6 +1405,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
#endif
if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) {
LOGI("Tunnel host/port is set, "
"--force-adb-forward automatically enabled.");
opts->force_adb_forward = true;
}
int index = optind;
if (index < argc) {
LOGE("Unexpected additional argument: %s", argv[index]);

View File

@@ -5,6 +5,7 @@
#include "adb.h"
#include "util/log.h"
#include "util/process_intr.h"
#define DEFAULT_PUSH_TARGET "/sdcard/Download/"
@@ -16,6 +17,7 @@ file_handler_request_destroy(struct file_handler_request *req) {
bool
file_handler_init(struct file_handler *file_handler, const char *serial,
const char *push_target) {
assert(serial);
cbuf_init(&file_handler->queue);
@@ -30,23 +32,26 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
return false;
}
if (serial) {
file_handler->serial = strdup(serial);
if (!file_handler->serial) {
LOGW("Could not strdup serial");
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
return false;
}
} else {
file_handler->serial = NULL;
ok = sc_intr_init(&file_handler->intr);
if (!ok) {
LOGE("Could not create intr");
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
}
file_handler->serial = strdup(serial);
if (!file_handler->serial) {
LOGE("Could not strdup serial");
sc_intr_destroy(&file_handler->intr);
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
return false;
}
// lazy initialization
file_handler->initialized = false;
file_handler->stopped = false;
file_handler->current_process = SC_PROCESS_NONE;
file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET;
@@ -57,6 +62,7 @@ void
file_handler_destroy(struct file_handler *file_handler) {
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
sc_intr_destroy(&file_handler->intr);
free(file_handler->serial);
struct file_handler_request req;
@@ -65,16 +71,6 @@ file_handler_destroy(struct file_handler *file_handler) {
}
}
static sc_pid
install_apk(const char *serial, const char *file) {
return adb_install(serial, file);
}
static sc_pid
push_file(const char *serial, const char *file, const char *push_target) {
return adb_push(serial, file, push_target);
}
bool
file_handler_request(struct file_handler *file_handler,
file_handler_action_t action, char *file) {
@@ -106,10 +102,16 @@ file_handler_request(struct file_handler *file_handler,
static int
run_file_handler(void *data) {
struct file_handler *file_handler = data;
struct sc_intr *intr = &file_handler->intr;
const char *serial = file_handler->serial;
assert(serial);
const char *push_target = file_handler->push_target;
assert(push_target);
for (;;) {
sc_mutex_lock(&file_handler->mutex);
file_handler->current_process = SC_PROCESS_NONE;
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
sc_cond_wait(&file_handler->event_cond, &file_handler->mutex);
}
@@ -122,43 +124,26 @@ run_file_handler(void *data) {
bool non_empty = cbuf_take(&file_handler->queue, &req);
assert(non_empty);
(void) non_empty;
sc_pid pid;
if (req.action == ACTION_INSTALL_APK) {
LOGI("Installing %s...", req.file);
pid = install_apk(file_handler->serial, req.file);
} else {
LOGI("Pushing %s...", req.file);
pid = push_file(file_handler->serial, req.file,
file_handler->push_target);
}
file_handler->current_process = pid;
sc_mutex_unlock(&file_handler->mutex);
if (req.action == ACTION_INSTALL_APK) {
if (sc_process_check_success(pid, "adb install", false)) {
LOGI("Installing %s...", req.file);
bool ok = adb_install(intr, serial, req.file);
if (ok) {
LOGI("%s successfully installed", req.file);
} else {
LOGE("Failed to install %s", req.file);
}
} else {
if (sc_process_check_success(pid, "adb push", false)) {
LOGI("%s successfully pushed to %s", req.file,
file_handler->push_target);
LOGI("Pushing %s...", req.file);
bool ok = adb_push(intr, serial, req.file, push_target);
if (ok) {
LOGI("%s successfully pushed to %s", req.file, push_target);
} else {
LOGE("Failed to push %s to %s", req.file,
file_handler->push_target);
LOGE("Failed to push %s to %s", req.file, push_target);
}
}
sc_mutex_lock(&file_handler->mutex);
// Close the process (it is necessarily already terminated)
// Execute this call with mutex locked to avoid race conditions with
// file_handler_stop()
sc_process_close(file_handler->current_process);
file_handler->current_process = SC_PROCESS_NONE;
sc_mutex_unlock(&file_handler->mutex);
file_handler_request_destroy(&req);
}
return 0;
@@ -183,11 +168,7 @@ file_handler_stop(struct file_handler *file_handler) {
sc_mutex_lock(&file_handler->mutex);
file_handler->stopped = true;
sc_cond_signal(&file_handler->event_cond);
if (file_handler->current_process != SC_PROCESS_NONE) {
if (!sc_process_terminate(file_handler->current_process)) {
LOGW("Could not terminate push/install process");
}
}
sc_intr_interrupt(&file_handler->intr);
sc_mutex_unlock(&file_handler->mutex);
}

View File

@@ -8,6 +8,7 @@
#include "adb.h"
#include "util/cbuf.h"
#include "util/thread.h"
#include "util/intr.h"
typedef enum {
ACTION_INSTALL_APK,
@@ -29,8 +30,9 @@ struct file_handler {
sc_cond event_cond;
bool stopped;
bool initialized;
sc_pid current_process;
struct file_handler_request_queue queue;
struct sc_intr intr;
};
bool

View File

@@ -47,6 +47,9 @@ main(int argc, char *argv[]) {
setbuf(stderr, NULL);
#endif
printf("scrcpy " SCRCPY_VERSION
" <https://github.com/Genymobile/scrcpy>\n");
struct scrcpy_cli_args args = {
.opts = scrcpy_options_default,
.help = false,
@@ -73,8 +76,6 @@ main(int argc, char *argv[]) {
return 0;
}
LOGI("scrcpy " SCRCPY_VERSION " <https://github.com/Genymobile/scrcpy>");
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
av_register_all();
#endif

View File

@@ -19,6 +19,8 @@ const struct scrcpy_options scrcpy_options_default = {
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
.last = DEFAULT_LOCAL_PORT_RANGE_LAST,
},
.tunnel_host = 0,
.tunnel_port = 0,
.shortcut_mods = {
.data = {SC_MOD_LALT, SC_MOD_LSUPER},
.count = 2,

View File

@@ -77,6 +77,8 @@ struct scrcpy_options {
enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode;
struct sc_port_range port_range;
uint32_t tunnel_host;
uint16_t tunnel_port;
struct sc_shortcut_mods shortcut_mods;
uint16_t max_size;
uint32_t bit_rate;

View File

@@ -345,6 +345,8 @@ scrcpy(struct scrcpy_options *options) {
.log_level = options->log_level,
.crop = options->crop,
.port_range = options->port_range,
.tunnel_host = options->tunnel_host,
.tunnel_port = options->tunnel_port,
.max_size = options->max_size,
.bit_rate = options->bit_rate,
.max_fps = options->max_fps,

View File

@@ -112,9 +112,9 @@ push_server(struct sc_intr *intr, const char *serial) {
free(server_path);
return false;
}
sc_pid pid = adb_push(serial, server_path, SC_DEVICE_SERVER_PATH);
bool ok = adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH);
free(server_path);
return sc_process_check_success_intr(intr, pid, "adb push");
return ok;
}
static const char *
@@ -202,8 +202,9 @@ execute_server(struct sc_server *server,
}
static bool
connect_and_read_byte(struct sc_intr *intr, sc_socket socket, uint16_t port) {
bool ok = net_connect_intr(intr, socket, IPV4_LOCALHOST, port);
connect_and_read_byte(struct sc_intr *intr, sc_socket socket,
uint32_t tunnel_host, uint16_t tunnel_port) {
bool ok = net_connect_intr(intr, socket, tunnel_host, tunnel_port);
if (!ok) {
return false;
}
@@ -220,13 +221,13 @@ connect_and_read_byte(struct sc_intr *intr, sc_socket socket, uint16_t port) {
}
static sc_socket
connect_to_server(struct sc_server *server, uint32_t attempts, sc_tick delay) {
uint16_t port = server->tunnel.local_port;
connect_to_server(struct sc_server *server, uint32_t attempts, sc_tick delay,
uint32_t host, uint16_t port) {
do {
LOGD("Remaining connection attempts: %d", (int) attempts);
sc_socket socket = net_socket();
if (socket != SC_SOCKET_NONE) {
bool ok = connect_and_read_byte(&server->intr, socket, port);
bool ok = connect_and_read_byte(&server->intr, socket, host, port);
if (ok) {
// it worked!
return socket;
@@ -352,9 +353,20 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
goto fail;
}
} else {
uint32_t tunnel_host = server->params.tunnel_host;
if (!tunnel_host) {
tunnel_host = IPV4_LOCALHOST;
}
uint16_t tunnel_port = server->params.tunnel_port;
if (!tunnel_port) {
tunnel_port = tunnel->local_port;
}
uint32_t attempts = 100;
sc_tick delay = SC_TICK_FROM_MS(100);
video_socket = connect_to_server(server, attempts, delay);
video_socket = connect_to_server(server, attempts, delay, tunnel_host,
tunnel_port);
if (video_socket == SC_SOCKET_NONE) {
goto fail;
}
@@ -364,8 +376,8 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
if (control_socket == SC_SOCKET_NONE) {
goto fail;
}
bool ok = net_connect_intr(&server->intr, control_socket,
IPV4_LOCALHOST, tunnel->local_port);
bool ok = net_connect_intr(&server->intr, control_socket, tunnel_host,
tunnel_port);
if (!ok) {
goto fail;
}
@@ -422,29 +434,6 @@ sc_server_on_terminated(void *userdata) {
LOGD("Server terminated");
}
static char *
sc_server_get_serialno(struct sc_intr *intr) {
sc_pipe pout;
sc_pid pid = adb_get_serialno(&pout);
if (pid == SC_PROCESS_NONE) {
return false;
}
char buf[128];
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf));
sc_pipe_close(pout);
bool ok = sc_process_check_success_intr(intr, pid, "adb get-serialno");
sc_process_close(pid);
if (!ok) {
return NULL;
}
sc_str_truncate(buf, r, " \r\n");
return strdup(buf);
}
static bool
sc_server_fill_serial(struct sc_server *server) {
// Retrieve the actual device immediately if not provided, so that all
@@ -453,7 +442,7 @@ sc_server_fill_serial(struct sc_server *server) {
// device/emulator" error)
if (!server->params.serial) {
// The serial is owned by sc_server_params, and will be freed on destroy
server->params.serial = sc_server_get_serialno(&server->intr);
server->params.serial = adb_get_serialno(&server->intr);
if (!server->params.serial) {
LOGE("Could not get device serial");
return false;

View File

@@ -29,6 +29,8 @@ struct sc_server_params {
const char *codec_options;
const char *encoder_name;
struct sc_port_range port_range;
uint32_t tunnel_host;
uint16_t tunnel_port;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;

View File

@@ -1,3 +1,7 @@
// For inet_pton() on Windows
#define _WIN32_WINNT 0x0600
#define WINVER 0x0600
#include "net.h"
#include <assert.h>
@@ -7,6 +11,7 @@
#include "log.h"
#ifdef __WINDOWS__
# include <ws2tcpip.h>
typedef int socklen_t;
typedef SOCKET sc_raw_socket;
#else
@@ -225,3 +230,15 @@ net_close(sc_socket socket) {
return !close(raw_sock);
#endif
}
bool
net_parse_ipv4(const char *s, uint32_t *ipv4) {
struct in_addr addr;
if (!inet_pton(AF_INET, s, &addr)) {
LOGE("Invalid IPv4 address: %s", s);
return false;
}
*ipv4 = ntohl(addr.s_addr);
return true;
}

View File

@@ -68,4 +68,10 @@ net_interrupt(sc_socket socket);
bool
net_close(sc_socket socket);
/**
* Parse `ip` "xxx.xxx.xxx.xxx" to an IPv4 host representation
*/
bool
net_parse_ipv4(const char *ip, uint32_t *ipv4);
#endif

View File

@@ -2,7 +2,7 @@
bool
sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid,
const char *name) {
const char *name, bool close) {
if (!sc_intr_set_process(intr, pid)) {
// Already interrupted
return false;
@@ -12,6 +12,12 @@ sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid,
bool ret = sc_process_check_success(pid, name, false);
sc_intr_set_process(intr, SC_PROCESS_NONE);
if (close) {
// Close separately
sc_process_close(pid);
}
return ret;
}

View File

@@ -8,7 +8,7 @@
bool
sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid,
const char *name);
const char *name, bool close);
ssize_t
sc_pipe_read_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data,

View File

@@ -295,10 +295,7 @@ error:
size_t
sc_str_truncate(char *data, size_t len, const char *endchars) {
data[len - 1] = '\0';
char *eol = strpbrk(data, endchars);
if (eol) {
*eol = '\0';
len = eol - data;
}
return len;
size_t idx = strcspn(data, endchars);
data[idx] = '\0';
return idx;
}

View File

@@ -339,22 +339,28 @@ static void test_wrap_lines(void) {
static void test_truncate(void) {
char s[] = "hello\nworld\n!";
size_t line_len = sc_str_truncate(s, sizeof(s), "\n");
size_t len = sc_str_truncate(s, sizeof(s), "\n");
assert(line_len == 5);
assert(len == 5);
assert(!strcmp("hello", s));
char s2[] = "hello\r\nworkd\r\n!";
line_len = sc_str_truncate(s2, sizeof(s2), "\n\r");
len = sc_str_truncate(s2, sizeof(s2), "\n\r");
assert(line_len == 5);
assert(len == 5);
assert(!strcmp("hello", s));
char s3[] = "hello world\n!";
line_len = sc_str_truncate(s3, sizeof(s3), " \n\r");
len = sc_str_truncate(s3, sizeof(s3), " \n\r");
assert(line_len == 5);
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));
}
int main(int argc, char *argv[]) {