Accept port range
Accept a range of ports to listen to, so that it does not fail if another instance of scrcpy is currently starting. The range can be passed via the command line: scrcpy -p 27183:27186 scrcpy -p 27183 # implicitly 27183:27183, as before The default is 27183:27199. Closes #951 <https://github.com/Genymobile/scrcpy/issues/951>
This commit is contained in:
parent
2a3a9d4ea9
commit
dc7fcf3c7a
9 changed files with 157 additions and 41 deletions
|
@ -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, \
|
||||
|
|
|
@ -141,29 +141,91 @@ listen_on_port(uint16_t port) {
|
|||
}
|
||||
|
||||
static bool
|
||||
enable_tunnel(struct server *server) {
|
||||
if (enable_tunnel_reverse(server->serial, server->local_port)) {
|
||||
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(server->local_port);
|
||||
if (server->server_socket == INVALID_SOCKET) {
|
||||
LOGE("Could not listen on port %" PRIu16, server->local_port);
|
||||
disable_tunnel(server);
|
||||
return false;
|
||||
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'");
|
||||
server->tunnel_forward = true;
|
||||
return enable_tunnel_forward(server->serial, server->local_port);
|
||||
return enable_tunnel_forward_any_port(server, port_range);
|
||||
}
|
||||
|
||||
static process_t
|
||||
|
@ -261,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);
|
||||
|
@ -275,7 +337,7 @@ 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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue