Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
7a547bf467 | ||
|
99989ec48a | ||
|
0b62ee209f | ||
|
e9e73a4cd7 |
@ -96,9 +96,10 @@ conf.set_quoted('PREFIX', get_option('prefix'))
|
||||
# directory as the executable)
|
||||
conf.set('PORTABLE', get_option('portable'))
|
||||
|
||||
# the default client TCP port for the "adb reverse" tunnel
|
||||
# the default client TCP port range for the "adb reverse" tunnel
|
||||
# overridden by option --port
|
||||
conf.set('DEFAULT_LOCAL_PORT', '27183')
|
||||
conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183')
|
||||
conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
|
||||
|
||||
# the default max video size for both dimensions, in pixels
|
||||
# overridden by option --max-size
|
||||
|
@ -60,10 +60,10 @@ Disable device control (mirror the device in read\-only).
|
||||
Do not display device (only when screen recording is enabled).
|
||||
|
||||
.TP
|
||||
.BI "\-p, \-\-port " port
|
||||
Set the TCP port the client listens on.
|
||||
.BI "\-p, \-\-port " port[:port]
|
||||
Set the TCP port (range) used by the client to listen.
|
||||
|
||||
Default is 27183.
|
||||
Default is 27183:27199.
|
||||
|
||||
.TP
|
||||
.B \-\-prefer\-text
|
||||
|
@ -58,9 +58,9 @@ scrcpy_print_usage(const char *arg0) {
|
||||
" Do not display device (only when screen recording is\n"
|
||||
" enabled).\n"
|
||||
"\n"
|
||||
" -p, --port port\n"
|
||||
" Set the TCP port the client listens on.\n"
|
||||
" Default is %d.\n"
|
||||
" -p, --port port[:port]\n"
|
||||
" Set the TCP port (range) used by the client to listen.\n"
|
||||
" Default is %d:%d.\n"
|
||||
"\n"
|
||||
" --prefer-text\n"
|
||||
" Inject alpha characters and space as text events instead of\n"
|
||||
@ -193,7 +193,7 @@ scrcpy_print_usage(const char *arg0) {
|
||||
arg0,
|
||||
DEFAULT_BIT_RATE,
|
||||
DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
|
||||
DEFAULT_LOCAL_PORT);
|
||||
DEFAULT_LOCAL_PORT_RANGE_FIRST, DEFAULT_LOCAL_PORT_RANGE_LAST);
|
||||
}
|
||||
|
||||
static bool
|
||||
@ -221,6 +221,27 @@ parse_integer_arg(const char *s, long *out, bool accept_suffix, long min,
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t
|
||||
parse_integers_arg(const char *s, size_t max_items, long *out, long min,
|
||||
long max, const char *name) {
|
||||
size_t count = parse_integers(s, ':', max_items, out);
|
||||
if (!count) {
|
||||
LOGE("Could not parse %s: %s", name, s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
long value = out[i];
|
||||
if (value < min || value > max) {
|
||||
LOGE("Could not parse %s: value (%ld) out-of-range (%ld; %ld)",
|
||||
name, value, min, max);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_bit_rate(const char *s, uint32_t *bit_rate) {
|
||||
long value;
|
||||
@ -286,14 +307,30 @@ parse_window_dimension(const char *s, uint16_t *dimension) {
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_port(const char *s, uint16_t *port) {
|
||||
long value;
|
||||
bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "port");
|
||||
if (!ok) {
|
||||
parse_port_range(const char *s, struct port_range *port_range) {
|
||||
long values[2];
|
||||
size_t count = parse_integers_arg(s, 2, values, 0, 0xFFFF, "port");
|
||||
if (!count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*port = (uint16_t) value;
|
||||
uint16_t v0 = (uint16_t) values[0];
|
||||
if (count == 1) {
|
||||
port_range->first = v0;
|
||||
port_range->last = v0;
|
||||
return true;
|
||||
}
|
||||
|
||||
assert(count == 2);
|
||||
uint16_t v1 = (uint16_t) values[1];
|
||||
if (v0 < v1) {
|
||||
port_range->first = v0;
|
||||
port_range->last = v1;
|
||||
} else {
|
||||
port_range->first = v1;
|
||||
port_range->last = v0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -424,7 +461,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||
opts->display = false;
|
||||
break;
|
||||
case 'p':
|
||||
if (!parse_port(optarg, &opts->port)) {
|
||||
if (!parse_port_range(optarg, &opts->port_range)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
@ -27,4 +27,9 @@ struct position {
|
||||
struct point point;
|
||||
};
|
||||
|
||||
struct port_range {
|
||||
uint16_t first;
|
||||
uint16_t last;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -280,7 +280,7 @@ scrcpy(const struct scrcpy_options *options) {
|
||||
bool record = !!options->record_filename;
|
||||
struct server_params params = {
|
||||
.crop = options->crop,
|
||||
.local_port = options->port,
|
||||
.port_range = options->port_range,
|
||||
.max_size = options->max_size,
|
||||
.bit_rate = options->bit_rate,
|
||||
.max_fps = options->max_fps,
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "common.h"
|
||||
#include "input_manager.h"
|
||||
#include "recorder.h"
|
||||
|
||||
@ -15,7 +16,7 @@ struct scrcpy_options {
|
||||
const char *window_title;
|
||||
const char *push_target;
|
||||
enum recorder_format record_format;
|
||||
uint16_t port;
|
||||
struct port_range port_range;
|
||||
uint16_t max_size;
|
||||
uint32_t bit_rate;
|
||||
uint16_t max_fps;
|
||||
@ -41,7 +42,10 @@ struct scrcpy_options {
|
||||
.window_title = NULL, \
|
||||
.push_target = NULL, \
|
||||
.record_format = RECORDER_FORMAT_AUTO, \
|
||||
.port = DEFAULT_LOCAL_PORT, \
|
||||
.port_range = { \
|
||||
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \
|
||||
.last = DEFAULT_LOCAL_PORT_RANGE_LAST, \
|
||||
}, \
|
||||
.max_size = DEFAULT_MAX_SIZE, \
|
||||
.bit_rate = DEFAULT_BIT_RATE, \
|
||||
.max_fps = 0, \
|
||||
|
135
app/src/server.c
135
app/src/server.c
@ -126,17 +126,6 @@ disable_tunnel_forward(const char *serial, uint16_t local_port) {
|
||||
return process_check_success(process, "adb forward --remove");
|
||||
}
|
||||
|
||||
static bool
|
||||
enable_tunnel(struct server *server) {
|
||||
if (enable_tunnel_reverse(server->serial, server->local_port)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGW("'adb reverse' failed, fallback to 'adb forward'");
|
||||
server->tunnel_forward = true;
|
||||
return enable_tunnel_forward(server->serial, server->local_port);
|
||||
}
|
||||
|
||||
static bool
|
||||
disable_tunnel(struct server *server) {
|
||||
if (server->tunnel_forward) {
|
||||
@ -145,6 +134,100 @@ disable_tunnel(struct server *server) {
|
||||
return disable_tunnel_reverse(server->serial);
|
||||
}
|
||||
|
||||
static socket_t
|
||||
listen_on_port(uint16_t port) {
|
||||
#define IPV4_LOCALHOST 0x7F000001
|
||||
return net_listen(IPV4_LOCALHOST, port, 1);
|
||||
}
|
||||
|
||||
static bool
|
||||
enable_tunnel_reverse_any_port(struct server *server,
|
||||
struct port_range port_range) {
|
||||
uint16_t port = port_range.first;
|
||||
for (;;) {
|
||||
if (!enable_tunnel_reverse(server->serial, port)) {
|
||||
// the command itself failed, it will fail on any port
|
||||
return false;
|
||||
}
|
||||
|
||||
// At the application level, the device part is "the server" because it
|
||||
// serves video stream and control. However, at the network level, the
|
||||
// client listens and the server connects to the client. That way, the
|
||||
// client can listen before starting the server app, so there is no
|
||||
// need to try to connect until the server socket is listening on the
|
||||
// device.
|
||||
server->server_socket = listen_on_port(port);
|
||||
if (server->server_socket != INVALID_SOCKET) {
|
||||
// success
|
||||
server->local_port = port;
|
||||
return true;
|
||||
}
|
||||
|
||||
// failure, disable tunnel and try another port
|
||||
if (!disable_tunnel_reverse(server->serial)) {
|
||||
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
|
||||
}
|
||||
|
||||
// check before incrementing to avoid overflow on port 65535
|
||||
if (port < port_range.last) {
|
||||
LOGW("Could not listen on port %" PRIu16", retrying on %" PRIu16,
|
||||
port, port + 1);
|
||||
port++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (port_range.first == port_range.last) {
|
||||
LOGE("Could not listen on port %" PRIu16, port_range.first);
|
||||
} else {
|
||||
LOGE("Could not listen on any port in range %" PRIu16 ":%" PRIu16,
|
||||
port_range.first, port_range.last);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
enable_tunnel_forward_any_port(struct server *server,
|
||||
struct port_range port_range) {
|
||||
server->tunnel_forward = true;
|
||||
uint16_t port = port_range.first;
|
||||
for (;;) {
|
||||
if (enable_tunnel_forward(server->serial, port)) {
|
||||
// success
|
||||
server->local_port = port;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (port < port_range.last) {
|
||||
LOGW("Could not forward port %" PRIu16", retrying on %" PRIu16,
|
||||
port, port + 1);
|
||||
port++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (port_range.first == port_range.last) {
|
||||
LOGE("Could not forward port %" PRIu16, port_range.first);
|
||||
} else {
|
||||
LOGE("Could not forward any port in range %" PRIu16 ":%" PRIu16,
|
||||
port_range.first, port_range.last);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
enable_tunnel_any_port(struct server *server, struct port_range port_range) {
|
||||
if (enable_tunnel_reverse_any_port(server, port_range)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to
|
||||
// "adb forward", so the app socket is the client
|
||||
|
||||
LOGW("'adb reverse' failed, fallback to 'adb forward'");
|
||||
return enable_tunnel_forward_any_port(server, port_range);
|
||||
}
|
||||
|
||||
static process_t
|
||||
execute_server(struct server *server, const struct server_params *params) {
|
||||
char max_size_string[6];
|
||||
@ -187,13 +270,6 @@ execute_server(struct server *server, const struct server_params *params) {
|
||||
return adb_execute(server->serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
|
||||
}
|
||||
|
||||
#define IPV4_LOCALHOST 0x7F000001
|
||||
|
||||
static socket_t
|
||||
listen_on_port(uint16_t port) {
|
||||
return net_listen(IPV4_LOCALHOST, port, 1);
|
||||
}
|
||||
|
||||
static socket_t
|
||||
connect_and_read_byte(uint16_t port) {
|
||||
socket_t socket = net_connect(IPV4_LOCALHOST, port);
|
||||
@ -247,7 +323,7 @@ server_init(struct server *server) {
|
||||
bool
|
||||
server_start(struct server *server, const char *serial,
|
||||
const struct server_params *params) {
|
||||
server->local_port = params->local_port;
|
||||
server->port_range = params->port_range;
|
||||
|
||||
if (serial) {
|
||||
server->serial = SDL_strdup(serial);
|
||||
@ -261,30 +337,11 @@ server_start(struct server *server, const char *serial,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!enable_tunnel(server)) {
|
||||
if (!enable_tunnel_any_port(server, params->port_range)) {
|
||||
SDL_free(server->serial);
|
||||
return false;
|
||||
}
|
||||
|
||||
// if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to
|
||||
// "adb forward", so the app socket is the client
|
||||
if (!server->tunnel_forward) {
|
||||
// At the application level, the device part is "the server" because it
|
||||
// serves video stream and control. However, at the network level, the
|
||||
// client listens and the server connects to the client. That way, the
|
||||
// client can listen before starting the server app, so there is no
|
||||
// need to try to connect until the server socket is listening on the
|
||||
// device.
|
||||
|
||||
server->server_socket = listen_on_port(params->local_port);
|
||||
if (server->server_socket == INVALID_SOCKET) {
|
||||
LOGE("Could not listen on port %" PRIu16, params->local_port);
|
||||
disable_tunnel(server);
|
||||
SDL_free(server->serial);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// server will connect to our server socket
|
||||
server->process = execute_server(server, params);
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "command.h"
|
||||
#include "common.h"
|
||||
#include "util/net.h"
|
||||
|
||||
struct server {
|
||||
@ -14,7 +15,8 @@ struct server {
|
||||
socket_t server_socket; // only used if !tunnel_forward
|
||||
socket_t video_socket;
|
||||
socket_t control_socket;
|
||||
uint16_t local_port;
|
||||
struct port_range port_range;
|
||||
uint16_t local_port; // selected from port_range
|
||||
bool tunnel_enabled;
|
||||
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
|
||||
};
|
||||
@ -25,6 +27,10 @@ struct server {
|
||||
.server_socket = INVALID_SOCKET, \
|
||||
.video_socket = INVALID_SOCKET, \
|
||||
.control_socket = INVALID_SOCKET, \
|
||||
.port_range = { \
|
||||
.first = 0, \
|
||||
.last = 0, \
|
||||
}, \
|
||||
.local_port = 0, \
|
||||
.tunnel_enabled = false, \
|
||||
.tunnel_forward = false, \
|
||||
@ -32,7 +38,7 @@ struct server {
|
||||
|
||||
struct server_params {
|
||||
const char *crop;
|
||||
uint16_t local_port;
|
||||
struct port_range port_range;
|
||||
uint16_t max_size;
|
||||
uint32_t bit_rate;
|
||||
uint16_t max_fps;
|
||||
|
@ -81,6 +81,35 @@ parse_integer(const char *s, long *out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t
|
||||
parse_integers(const char *s, const char sep, size_t max_items, long *out) {
|
||||
size_t count = 0;
|
||||
char *endptr;
|
||||
do {
|
||||
errno = 0;
|
||||
long value = strtol(s, &endptr, 0);
|
||||
if (errno == ERANGE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (endptr == s || (*endptr != sep && *endptr != '\0')) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
out[count++] = value;
|
||||
if (*endptr == sep) {
|
||||
if (count >= max_items) {
|
||||
// max items already reached, could not accept a new item
|
||||
return 0;
|
||||
}
|
||||
// parse the next token during the next iteration
|
||||
s = endptr + 1;
|
||||
}
|
||||
} while (*endptr != '\0');
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
bool
|
||||
parse_integer_with_suffix(const char *s, long *out) {
|
||||
char *endptr;
|
||||
|
@ -31,6 +31,11 @@ strquote(const char *src);
|
||||
bool
|
||||
parse_integer(const char *s, long *out);
|
||||
|
||||
// parse s as integers separated by sep (for example '1234:2000')
|
||||
// returns the number of integers on success, 0 on failure
|
||||
size_t
|
||||
parse_integers(const char *s, const char sep, size_t max_items, long *out);
|
||||
|
||||
// parse s as an integer into value
|
||||
// like parse_integer(), but accept 'k'/'K' (x1000) and 'm'/'M' (x1000000) as
|
||||
// suffix
|
||||
|
@ -50,7 +50,7 @@ static void test_options(void) {
|
||||
"--max-size", "1024",
|
||||
// "--no-control" is not compatible with "--turn-screen-off"
|
||||
// "--no-display" is not compatible with "--fulscreen"
|
||||
"--port", "1234",
|
||||
"--port", "1234:1236",
|
||||
"--push-target", "/sdcard/Movies",
|
||||
"--record", "file",
|
||||
"--record-format", "mkv",
|
||||
@ -78,7 +78,8 @@ static void test_options(void) {
|
||||
assert(opts->fullscreen);
|
||||
assert(opts->max_fps == 30);
|
||||
assert(opts->max_size == 1024);
|
||||
assert(opts->port == 1234);
|
||||
assert(opts->port_range.first == 1234);
|
||||
assert(opts->port_range.last == 1236);
|
||||
assert(!strcmp(opts->push_target, "/sdcard/Movies"));
|
||||
assert(!strcmp(opts->record_filename, "file"));
|
||||
assert(opts->record_format == RECORDER_FORMAT_MKV);
|
||||
|
@ -187,6 +187,55 @@ static void test_parse_integer(void) {
|
||||
assert(!ok); // out-of-range
|
||||
}
|
||||
|
||||
static void test_parse_integers(void) {
|
||||
long values[5];
|
||||
|
||||
size_t count = parse_integers("1234", ':', 5, values);
|
||||
assert(count == 1);
|
||||
assert(values[0] == 1234);
|
||||
|
||||
count = parse_integers("1234:5678", ':', 5, values);
|
||||
assert(count == 2);
|
||||
assert(values[0] == 1234);
|
||||
assert(values[1] == 5678);
|
||||
|
||||
count = parse_integers("1234:5678", ':', 2, values);
|
||||
assert(count == 2);
|
||||
assert(values[0] == 1234);
|
||||
assert(values[1] == 5678);
|
||||
|
||||
count = parse_integers("1234:-5678", ':', 2, values);
|
||||
assert(count == 2);
|
||||
assert(values[0] == 1234);
|
||||
assert(values[1] == -5678);
|
||||
|
||||
count = parse_integers("1:2:3:4:5", ':', 5, values);
|
||||
assert(count == 5);
|
||||
assert(values[0] == 1);
|
||||
assert(values[1] == 2);
|
||||
assert(values[2] == 3);
|
||||
assert(values[3] == 4);
|
||||
assert(values[4] == 5);
|
||||
|
||||
count = parse_integers("1234:5678", ':', 1, values);
|
||||
assert(count == 0); // max_items == 1
|
||||
|
||||
count = parse_integers("1:2:3:4:5", ':', 3, values);
|
||||
assert(count == 0); // max_items == 3
|
||||
|
||||
count = parse_integers(":1234", ':', 5, values);
|
||||
assert(count == 0); // invalid
|
||||
|
||||
count = parse_integers("1234:", ':', 5, values);
|
||||
assert(count == 0); // invalid
|
||||
|
||||
count = parse_integers("1234:", ':', 1, values);
|
||||
assert(count == 0); // invalid, even when max_items == 1
|
||||
|
||||
count = parse_integers("1234::5678", ':', 5, values);
|
||||
assert(count == 0); // invalid
|
||||
}
|
||||
|
||||
static void test_parse_integer_with_suffix(void) {
|
||||
long value;
|
||||
bool ok = parse_integer_with_suffix("1234", &value);
|
||||
@ -249,6 +298,7 @@ int main(void) {
|
||||
test_strquote();
|
||||
test_utf8_truncate();
|
||||
test_parse_integer();
|
||||
test_parse_integers();
|
||||
test_parse_integer_with_suffix();
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user