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)
|
# directory as the executable)
|
||||||
conf.set('PORTABLE', get_option('portable'))
|
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
|
# 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
|
# the default max video size for both dimensions, in pixels
|
||||||
# overridden by option --max-size
|
# 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).
|
Do not display device (only when screen recording is enabled).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-p, \-\-port " port
|
.BI "\-p, \-\-port " port[:port]
|
||||||
Set the TCP port the client listens on.
|
Set the TCP port (range) used by the client to listen.
|
||||||
|
|
||||||
Default is 27183.
|
Default is 27183:27199.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-prefer\-text
|
.B \-\-prefer\-text
|
||||||
|
|
|
@ -58,9 +58,9 @@ scrcpy_print_usage(const char *arg0) {
|
||||||
" Do not display device (only when screen recording is\n"
|
" Do not display device (only when screen recording is\n"
|
||||||
" enabled).\n"
|
" enabled).\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -p, --port port\n"
|
" -p, --port port[:port]\n"
|
||||||
" Set the TCP port the client listens on.\n"
|
" Set the TCP port (range) used by the client to listen.\n"
|
||||||
" Default is %d.\n"
|
" Default is %d:%d.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --prefer-text\n"
|
" --prefer-text\n"
|
||||||
" Inject alpha characters and space as text events instead of\n"
|
" Inject alpha characters and space as text events instead of\n"
|
||||||
|
@ -193,7 +193,7 @@ scrcpy_print_usage(const char *arg0) {
|
||||||
arg0,
|
arg0,
|
||||||
DEFAULT_BIT_RATE,
|
DEFAULT_BIT_RATE,
|
||||||
DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
|
DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
|
||||||
DEFAULT_LOCAL_PORT);
|
DEFAULT_LOCAL_PORT_RANGE_FIRST, DEFAULT_LOCAL_PORT_RANGE_LAST);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
|
@ -221,6 +221,27 @@ parse_integer_arg(const char *s, long *out, bool accept_suffix, long min,
|
||||||
return true;
|
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
|
static bool
|
||||||
parse_bit_rate(const char *s, uint32_t *bit_rate) {
|
parse_bit_rate(const char *s, uint32_t *bit_rate) {
|
||||||
long value;
|
long value;
|
||||||
|
@ -286,14 +307,30 @@ parse_window_dimension(const char *s, uint16_t *dimension) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_port(const char *s, uint16_t *port) {
|
parse_port_range(const char *s, struct port_range *port_range) {
|
||||||
long value;
|
long values[2];
|
||||||
bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "port");
|
size_t count = parse_integers_arg(s, 2, values, 0, 0xFFFF, "port");
|
||||||
if (!ok) {
|
if (!count) {
|
||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,7 +461,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||||
opts->display = false;
|
opts->display = false;
|
||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
if (!parse_port(optarg, &opts->port)) {
|
if (!parse_port_range(optarg, &opts->port_range)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -27,4 +27,9 @@ struct position {
|
||||||
struct point point;
|
struct point point;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct port_range {
|
||||||
|
uint16_t first;
|
||||||
|
uint16_t last;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -280,7 +280,7 @@ scrcpy(const struct scrcpy_options *options) {
|
||||||
bool record = !!options->record_filename;
|
bool record = !!options->record_filename;
|
||||||
struct server_params params = {
|
struct server_params params = {
|
||||||
.crop = options->crop,
|
.crop = options->crop,
|
||||||
.local_port = options->port,
|
.port_range = options->port_range,
|
||||||
.max_size = options->max_size,
|
.max_size = options->max_size,
|
||||||
.bit_rate = options->bit_rate,
|
.bit_rate = options->bit_rate,
|
||||||
.max_fps = options->max_fps,
|
.max_fps = options->max_fps,
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "common.h"
|
||||||
#include "input_manager.h"
|
#include "input_manager.h"
|
||||||
#include "recorder.h"
|
#include "recorder.h"
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ struct scrcpy_options {
|
||||||
const char *window_title;
|
const char *window_title;
|
||||||
const char *push_target;
|
const char *push_target;
|
||||||
enum recorder_format record_format;
|
enum recorder_format record_format;
|
||||||
uint16_t port;
|
struct port_range port_range;
|
||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t bit_rate;
|
uint32_t bit_rate;
|
||||||
uint16_t max_fps;
|
uint16_t max_fps;
|
||||||
|
@ -41,7 +42,10 @@ struct scrcpy_options {
|
||||||
.window_title = NULL, \
|
.window_title = NULL, \
|
||||||
.push_target = NULL, \
|
.push_target = NULL, \
|
||||||
.record_format = RECORDER_FORMAT_AUTO, \
|
.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, \
|
.max_size = DEFAULT_MAX_SIZE, \
|
||||||
.bit_rate = DEFAULT_BIT_RATE, \
|
.bit_rate = DEFAULT_BIT_RATE, \
|
||||||
.max_fps = 0, \
|
.max_fps = 0, \
|
||||||
|
|
|
@ -141,29 +141,91 @@ listen_on_port(uint16_t port) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
enable_tunnel(struct server *server) {
|
enable_tunnel_reverse_any_port(struct server *server,
|
||||||
if (enable_tunnel_reverse(server->serial, server->local_port)) {
|
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
|
// At the application level, the device part is "the server" because it
|
||||||
// serves video stream and control. However, at the network level, the
|
// serves video stream and control. However, at the network level, the
|
||||||
// client listens and the server connects to the client. That way, 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
|
// 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
|
// need to try to connect until the server socket is listening on the
|
||||||
// device.
|
// device.
|
||||||
server->server_socket = listen_on_port(server->local_port);
|
server->server_socket = listen_on_port(port);
|
||||||
if (server->server_socket == INVALID_SOCKET) {
|
if (server->server_socket != INVALID_SOCKET) {
|
||||||
LOGE("Could not listen on port %" PRIu16, server->local_port);
|
// success
|
||||||
disable_tunnel(server);
|
server->local_port = port;
|
||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to
|
// if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to
|
||||||
// "adb forward", so the app socket is the client
|
// "adb forward", so the app socket is the client
|
||||||
|
|
||||||
LOGW("'adb reverse' failed, fallback to 'adb forward'");
|
LOGW("'adb reverse' failed, fallback to 'adb forward'");
|
||||||
server->tunnel_forward = true;
|
return enable_tunnel_forward_any_port(server, port_range);
|
||||||
return enable_tunnel_forward(server->serial, server->local_port);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static process_t
|
static process_t
|
||||||
|
@ -261,7 +323,7 @@ server_init(struct server *server) {
|
||||||
bool
|
bool
|
||||||
server_start(struct server *server, const char *serial,
|
server_start(struct server *server, const char *serial,
|
||||||
const struct server_params *params) {
|
const struct server_params *params) {
|
||||||
server->local_port = params->local_port;
|
server->port_range = params->port_range;
|
||||||
|
|
||||||
if (serial) {
|
if (serial) {
|
||||||
server->serial = SDL_strdup(serial);
|
server->serial = SDL_strdup(serial);
|
||||||
|
@ -275,7 +337,7 @@ server_start(struct server *server, const char *serial,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!enable_tunnel(server)) {
|
if (!enable_tunnel_any_port(server, params->port_range)) {
|
||||||
SDL_free(server->serial);
|
SDL_free(server->serial);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "command.h"
|
#include "command.h"
|
||||||
|
#include "common.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
|
||||||
struct server {
|
struct server {
|
||||||
|
@ -14,25 +15,30 @@ struct server {
|
||||||
socket_t server_socket; // only used if !tunnel_forward
|
socket_t server_socket; // only used if !tunnel_forward
|
||||||
socket_t video_socket;
|
socket_t video_socket;
|
||||||
socket_t control_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_enabled;
|
||||||
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
|
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SERVER_INITIALIZER { \
|
#define SERVER_INITIALIZER { \
|
||||||
.serial = NULL, \
|
.serial = NULL, \
|
||||||
.process = PROCESS_NONE, \
|
.process = PROCESS_NONE, \
|
||||||
.server_socket = INVALID_SOCKET, \
|
.server_socket = INVALID_SOCKET, \
|
||||||
.video_socket = INVALID_SOCKET, \
|
.video_socket = INVALID_SOCKET, \
|
||||||
.control_socket = INVALID_SOCKET, \
|
.control_socket = INVALID_SOCKET, \
|
||||||
.local_port = 0, \
|
.port_range = { \
|
||||||
.tunnel_enabled = false, \
|
.first = 0, \
|
||||||
.tunnel_forward = false, \
|
.last = 0, \
|
||||||
|
}, \
|
||||||
|
.local_port = 0, \
|
||||||
|
.tunnel_enabled = false, \
|
||||||
|
.tunnel_forward = false, \
|
||||||
}
|
}
|
||||||
|
|
||||||
struct server_params {
|
struct server_params {
|
||||||
const char *crop;
|
const char *crop;
|
||||||
uint16_t local_port;
|
struct port_range port_range;
|
||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t bit_rate;
|
uint32_t bit_rate;
|
||||||
uint16_t max_fps;
|
uint16_t max_fps;
|
||||||
|
|
|
@ -50,7 +50,7 @@ static void test_options(void) {
|
||||||
"--max-size", "1024",
|
"--max-size", "1024",
|
||||||
// "--no-control" is not compatible with "--turn-screen-off"
|
// "--no-control" is not compatible with "--turn-screen-off"
|
||||||
// "--no-display" is not compatible with "--fulscreen"
|
// "--no-display" is not compatible with "--fulscreen"
|
||||||
"--port", "1234",
|
"--port", "1234:1236",
|
||||||
"--push-target", "/sdcard/Movies",
|
"--push-target", "/sdcard/Movies",
|
||||||
"--record", "file",
|
"--record", "file",
|
||||||
"--record-format", "mkv",
|
"--record-format", "mkv",
|
||||||
|
@ -78,7 +78,8 @@ static void test_options(void) {
|
||||||
assert(opts->fullscreen);
|
assert(opts->fullscreen);
|
||||||
assert(opts->max_fps == 30);
|
assert(opts->max_fps == 30);
|
||||||
assert(opts->max_size == 1024);
|
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->push_target, "/sdcard/Movies"));
|
||||||
assert(!strcmp(opts->record_filename, "file"));
|
assert(!strcmp(opts->record_filename, "file"));
|
||||||
assert(opts->record_format == RECORDER_FORMAT_MKV);
|
assert(opts->record_format == RECORDER_FORMAT_MKV);
|
||||||
|
|
Loading…
Reference in a new issue