Make it work over tcpip
"adb reverse" currently does not work over tcpip (i.e. on a device connected by "adb connect"): <https://issuetracker.google.com/issues/37066218> To work around the problem, if the call to "adb reverse" fails, then fallback to "adb forward", and reverse the client/server roles. Keep the "adb reverse" mode as the default because it does not involve connection retries: when using "adb forward", the client must try to connect successively until the server listens. Due to the tunnel, every connect() will succeed, so the client must attempt to read() to detect a connection failure. For this purpose, when using the "adb forward" mode, the server initially writes a dummy byte, read by the client. Fixes <https://github.com/Genymobile/scrcpy/issues/5>.
This commit is contained in:
parent
2b3ed5bcdb
commit
1038bad385
9 changed files with 174 additions and 32 deletions
|
@ -45,6 +45,13 @@ process_t adb_forward(const char *serial, uint16_t local_port, const char *devic
|
||||||
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
|
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
process_t adb_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));
|
||||||
|
}
|
||||||
|
|
||||||
process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port) {
|
process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port) {
|
||||||
char local[4 + 5 + 1]; // tcp:PORT
|
char local[4 + 5 + 1]; // tcp:PORT
|
||||||
char remote[108 + 14 + 1]; // localabstract:NAME
|
char remote[108 + 14 + 1]; // localabstract:NAME
|
||||||
|
|
|
@ -39,6 +39,7 @@ SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code);
|
||||||
|
|
||||||
process_t adb_execute(const char *serial, const char *const adb_cmd[], int len);
|
process_t adb_execute(const char *serial, const char *const adb_cmd[], int len);
|
||||||
process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name);
|
process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name);
|
||||||
|
process_t adb_forward_remove(const char *serial, uint16_t local_port);
|
||||||
process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port);
|
process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port);
|
||||||
process_t adb_reverse_remove(const char *serial, const char *device_socket_name);
|
process_t adb_reverse_remove(const char *serial, const char *device_socket_name);
|
||||||
process_t adb_push(const char *serial, const char *local, const char *remote);
|
process_t adb_push(const char *serial, const char *local, const char *remote);
|
||||||
|
|
|
@ -18,6 +18,26 @@
|
||||||
typedef struct in_addr IN_ADDR;
|
typedef struct in_addr IN_ADDR;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
socket_t net_connect(Uint32 addr, Uint16 port) {
|
||||||
|
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (sock == INVALID_SOCKET) {
|
||||||
|
perror("socket");
|
||||||
|
return INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
SOCKADDR_IN sin;
|
||||||
|
sin.sin_family = AF_INET;
|
||||||
|
sin.sin_addr.s_addr = htonl(addr);
|
||||||
|
sin.sin_port = htons(port);
|
||||||
|
|
||||||
|
if (connect(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
|
||||||
|
perror("connect");
|
||||||
|
return INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
socket_t net_listen(Uint32 addr, Uint16 port, int backlog) {
|
socket_t net_listen(Uint32 addr, Uint16 port, int backlog) {
|
||||||
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
if (sock == INVALID_SOCKET) {
|
if (sock == INVALID_SOCKET) {
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
SDL_bool net_init(void);
|
SDL_bool net_init(void);
|
||||||
void net_cleanup(void);
|
void net_cleanup(void);
|
||||||
|
|
||||||
|
socket_t net_connect(Uint32 addr, Uint16 port);
|
||||||
socket_t net_listen(Uint32 addr, Uint16 port, int backlog);
|
socket_t net_listen(Uint32 addr, Uint16 port, int backlog);
|
||||||
socket_t net_accept(socket_t server_socket);
|
socket_t net_accept(socket_t server_socket);
|
||||||
|
|
||||||
|
|
131
app/src/server.c
131
app/src/server.c
|
@ -4,6 +4,7 @@
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <SDL2/SDL_assert.h>
|
#include <SDL2/SDL_assert.h>
|
||||||
|
#include <SDL2/SDL_timer.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
@ -37,17 +38,45 @@ static SDL_bool remove_server(const char *serial) {
|
||||||
return process_check_success(process, "adb shell rm");
|
return process_check_success(process, "adb shell rm");
|
||||||
}
|
}
|
||||||
|
|
||||||
static SDL_bool enable_tunnel(const char *serial, Uint16 local_port) {
|
static SDL_bool enable_tunnel_reverse(const char *serial, Uint16 local_port) {
|
||||||
process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
|
process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
|
||||||
return process_check_success(process, "adb reverse");
|
return process_check_success(process, "adb reverse");
|
||||||
}
|
}
|
||||||
|
|
||||||
static SDL_bool disable_tunnel(const char *serial) {
|
static SDL_bool disable_tunnel_reverse(const char *serial) {
|
||||||
process_t process = adb_reverse_remove(serial, SOCKET_NAME);
|
process_t process = adb_reverse_remove(serial, SOCKET_NAME);
|
||||||
return process_check_success(process, "adb reverse --remove");
|
return process_check_success(process, "adb reverse --remove");
|
||||||
}
|
}
|
||||||
|
|
||||||
static process_t execute_server(const char *serial, Uint16 max_size, Uint32 bit_rate) {
|
static SDL_bool enable_tunnel_forward(const char *serial, Uint16 local_port) {
|
||||||
|
process_t process = adb_forward(serial, local_port, SOCKET_NAME);
|
||||||
|
return process_check_success(process, "adb forward");
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDL_bool disable_tunnel_forward(const char *serial, Uint16 local_port) {
|
||||||
|
process_t process = adb_forward_remove(serial, local_port);
|
||||||
|
return process_check_success(process, "adb forward --remove");
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDL_bool enable_tunnel(struct server *server) {
|
||||||
|
if (enable_tunnel_reverse(server->serial, server->local_port)) {
|
||||||
|
return SDL_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGW("'adb reverse' failed, fallback to 'adb forward'");
|
||||||
|
server->tunnel_forward = SDL_TRUE;
|
||||||
|
return enable_tunnel_forward(server->serial, server->local_port);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDL_bool disable_tunnel(struct server *server) {
|
||||||
|
if (server->tunnel_forward) {
|
||||||
|
return disable_tunnel_forward(server->serial, server->local_port);
|
||||||
|
}
|
||||||
|
return disable_tunnel_reverse(server->serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
static process_t execute_server(const char *serial,
|
||||||
|
Uint16 max_size, Uint32 bit_rate, SDL_bool tunnel_forward) {
|
||||||
char max_size_string[6];
|
char max_size_string[6];
|
||||||
char bit_rate_string[11];
|
char bit_rate_string[11];
|
||||||
sprintf(max_size_string, "%"PRIu16, max_size);
|
sprintf(max_size_string, "%"PRIu16, max_size);
|
||||||
|
@ -60,15 +89,48 @@ static process_t execute_server(const char *serial, Uint16 max_size, Uint32 bit_
|
||||||
"com.genymobile.scrcpy.Server",
|
"com.genymobile.scrcpy.Server",
|
||||||
max_size_string,
|
max_size_string,
|
||||||
bit_rate_string,
|
bit_rate_string,
|
||||||
|
tunnel_forward ? "true" : "false",
|
||||||
};
|
};
|
||||||
return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
|
return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
static socket_t listen_on_port(Uint16 port) {
|
|
||||||
#define IPV4_LOCALHOST 0x7F000001
|
#define IPV4_LOCALHOST 0x7F000001
|
||||||
|
|
||||||
|
static socket_t listen_on_port(Uint16 port) {
|
||||||
return net_listen(IPV4_LOCALHOST, port, 1);
|
return net_listen(IPV4_LOCALHOST, port, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static socket_t connect_and_read_byte(Uint16 port) {
|
||||||
|
socket_t socket = net_connect(IPV4_LOCALHOST, port);
|
||||||
|
if (socket == INVALID_SOCKET) {
|
||||||
|
return INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
char byte;
|
||||||
|
// the connection may succeed even if the server behind the "adb tunnel"
|
||||||
|
// is not listening, so read one byte to detect a working connection
|
||||||
|
if (net_recv_all(socket, &byte, 1) != 1) {
|
||||||
|
// the server is not listening yet behind the adb tunnel
|
||||||
|
return INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
static socket_t connect_to_server(Uint16 port, Uint32 attempts, Uint32 delay) {
|
||||||
|
do {
|
||||||
|
LOGD("Remaining connection attempts: %d", (int) attempts);
|
||||||
|
socket_t socket = connect_and_read_byte(port);
|
||||||
|
if (socket != INVALID_SOCKET) {
|
||||||
|
// it worked!
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
if (attempts) {
|
||||||
|
SDL_Delay(delay);
|
||||||
|
}
|
||||||
|
} while (--attempts > 0);
|
||||||
|
return INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
static void close_socket(socket_t *socket) {
|
static void close_socket(socket_t *socket) {
|
||||||
SDL_assert(*socket != INVALID_SOCKET);
|
SDL_assert(*socket != INVALID_SOCKET);
|
||||||
net_shutdown(*socket, SHUT_RDWR);
|
net_shutdown(*socket, SHUT_RDWR);
|
||||||
|
@ -85,6 +147,8 @@ void server_init(struct server *server) {
|
||||||
|
|
||||||
SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port,
|
SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port,
|
||||||
Uint16 max_size, Uint32 bit_rate) {
|
Uint16 max_size, Uint32 bit_rate) {
|
||||||
|
server->local_port = local_port;
|
||||||
|
|
||||||
if (serial) {
|
if (serial) {
|
||||||
server->serial = SDL_strdup(serial);
|
server->serial = SDL_strdup(serial);
|
||||||
}
|
}
|
||||||
|
@ -95,52 +159,67 @@ SDL_bool server_start(struct server *server, const char *serial, Uint16 local_po
|
||||||
|
|
||||||
server->server_copied_to_device = SDL_TRUE;
|
server->server_copied_to_device = SDL_TRUE;
|
||||||
|
|
||||||
if (!enable_tunnel(serial, local_port)) {
|
if (!enable_tunnel(server)) {
|
||||||
return SDL_FALSE;
|
return SDL_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// At the application level, the device part is "the server" because it
|
// if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to
|
||||||
// serves video stream and control. However, at the network level, the
|
// "adb forward", so the app socket is the client
|
||||||
// client listens and the server connects to the client. That way, the
|
if (!server->tunnel_forward) {
|
||||||
// client can listen before starting the server app, so there is no need to
|
// At the application level, the device part is "the server" because it
|
||||||
// try to connect until the server socket is listening on the device.
|
// 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(local_port);
|
server->server_socket = listen_on_port(local_port);
|
||||||
if (server->server_socket == INVALID_SOCKET) {
|
if (server->server_socket == INVALID_SOCKET) {
|
||||||
LOGE("Could not listen on port %" PRIu16, local_port);
|
LOGE("Could not listen on port %" PRIu16, local_port);
|
||||||
disable_tunnel(serial);
|
disable_tunnel(server);
|
||||||
return SDL_FALSE;
|
return SDL_FALSE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// server will connect to our server socket
|
// server will connect to our server socket
|
||||||
server->process = execute_server(serial, max_size, bit_rate);
|
server->process = execute_server(serial, max_size, bit_rate, server->tunnel_forward);
|
||||||
if (server->process == PROCESS_NONE) {
|
if (server->process == PROCESS_NONE) {
|
||||||
close_socket(&server->server_socket);
|
if (!server->tunnel_forward) {
|
||||||
disable_tunnel(serial);
|
close_socket(&server->server_socket);
|
||||||
|
}
|
||||||
|
disable_tunnel(server);
|
||||||
return SDL_FALSE;
|
return SDL_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
server->adb_reverse_enabled = SDL_TRUE;
|
server->tunnel_enabled = SDL_TRUE;
|
||||||
|
|
||||||
return SDL_TRUE;
|
return SDL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
socket_t server_connect_to(struct server *server, Uint32 timeout_ms) {
|
socket_t server_connect_to(struct server *server, Uint32 timeout_ms) {
|
||||||
server->device_socket = net_accept(server->server_socket);
|
if (!server->tunnel_forward) {
|
||||||
|
server->device_socket = net_accept(server->server_socket);
|
||||||
|
} else {
|
||||||
|
Uint32 attempts = 10;
|
||||||
|
Uint32 delay = 100; // ms
|
||||||
|
server->device_socket = connect_to_server(server->local_port, attempts, delay);
|
||||||
|
}
|
||||||
|
|
||||||
if (server->device_socket == INVALID_SOCKET) {
|
if (server->device_socket == INVALID_SOCKET) {
|
||||||
return INVALID_SOCKET;
|
return INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we don't need the server socket anymore
|
if (!server->tunnel_forward) {
|
||||||
close_socket(&server->server_socket);
|
// we don't need the server socket anymore
|
||||||
|
close_socket(&server->server_socket);
|
||||||
|
}
|
||||||
|
|
||||||
// the server is started, we can clean up the jar from the temporary folder
|
// the server is started, we can clean up the jar from the temporary folder
|
||||||
remove_server(server->serial); // ignore failure
|
remove_server(server->serial); // ignore failure
|
||||||
server->server_copied_to_device = SDL_FALSE;
|
server->server_copied_to_device = SDL_FALSE;
|
||||||
|
|
||||||
// we don't need the adb tunnel anymore
|
// we don't need the adb tunnel anymore
|
||||||
disable_tunnel(server->serial); // ignore failure
|
disable_tunnel(server); // ignore failure
|
||||||
server->adb_reverse_enabled = SDL_FALSE;
|
server->tunnel_enabled = SDL_FALSE;
|
||||||
|
|
||||||
return server->device_socket;
|
return server->device_socket;
|
||||||
}
|
}
|
||||||
|
@ -155,9 +234,9 @@ void server_stop(struct server *server) {
|
||||||
cmd_simple_wait(server->process, NULL); // ignore exit code
|
cmd_simple_wait(server->process, NULL); // ignore exit code
|
||||||
LOGD("Server terminated");
|
LOGD("Server terminated");
|
||||||
|
|
||||||
if (server->adb_reverse_enabled) {
|
if (server->tunnel_enabled) {
|
||||||
// ignore failure
|
// ignore failure
|
||||||
disable_tunnel(server->serial);
|
disable_tunnel(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (server->server_copied_to_device) {
|
if (server->server_copied_to_device) {
|
||||||
|
|
|
@ -7,9 +7,11 @@
|
||||||
struct server {
|
struct server {
|
||||||
const char *serial;
|
const char *serial;
|
||||||
process_t process;
|
process_t process;
|
||||||
socket_t server_socket;
|
socket_t server_socket; // only used if !tunnel_forward
|
||||||
socket_t device_socket;
|
socket_t device_socket;
|
||||||
SDL_bool adb_reverse_enabled;
|
Uint16 local_port;
|
||||||
|
SDL_bool tunnel_enabled;
|
||||||
|
SDL_bool tunnel_forward; // use "adb forward" instead of "adb reverse"
|
||||||
SDL_bool server_copied_to_device;
|
SDL_bool server_copied_to_device;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,7 +20,9 @@ struct server {
|
||||||
.process = PROCESS_NONE, \
|
.process = PROCESS_NONE, \
|
||||||
.server_socket = INVALID_SOCKET, \
|
.server_socket = INVALID_SOCKET, \
|
||||||
.device_socket = INVALID_SOCKET, \
|
.device_socket = INVALID_SOCKET, \
|
||||||
.adb_reverse_enabled = SDL_FALSE, \
|
.local_port = 0, \
|
||||||
|
.tunnel_enabled = SDL_FALSE, \
|
||||||
|
.tunnel_forward = SDL_FALSE, \
|
||||||
.server_copied_to_device = SDL_FALSE, \
|
.server_copied_to_device = SDL_FALSE, \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import android.net.LocalServerSocket;
|
||||||
import android.net.LocalSocket;
|
import android.net.LocalSocket;
|
||||||
import android.net.LocalSocketAddress;
|
import android.net.LocalSocketAddress;
|
||||||
|
|
||||||
|
@ -33,8 +34,20 @@ public final class DesktopConnection implements Closeable {
|
||||||
return localSocket;
|
return localSocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DesktopConnection open(Device device) throws IOException {
|
private static LocalSocket listenAndAccept(String abstractName) throws IOException {
|
||||||
LocalSocket socket = connect(SOCKET_NAME);
|
LocalServerSocket localServerSocket = new LocalServerSocket(abstractName);
|
||||||
|
return localServerSocket.accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException {
|
||||||
|
LocalSocket socket;
|
||||||
|
if (tunnelForward) {
|
||||||
|
socket = listenAndAccept(SOCKET_NAME);
|
||||||
|
// send one byte so the client may read() to detect a connection error
|
||||||
|
socket.getOutputStream().write(0);
|
||||||
|
} else {
|
||||||
|
socket = connect(SOCKET_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
DesktopConnection connection = new DesktopConnection(socket);
|
DesktopConnection connection = new DesktopConnection(socket);
|
||||||
Size videoSize = device.getScreenInfo().getVideoSize();
|
Size videoSize = device.getScreenInfo().getVideoSize();
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.genymobile.scrcpy;
|
||||||
public class Options {
|
public class Options {
|
||||||
private int maxSize;
|
private int maxSize;
|
||||||
private int bitRate;
|
private int bitRate;
|
||||||
|
private boolean tunnelForward;
|
||||||
|
|
||||||
public int getMaxSize() {
|
public int getMaxSize() {
|
||||||
return maxSize;
|
return maxSize;
|
||||||
|
@ -19,4 +20,12 @@ public class Options {
|
||||||
public void setBitRate(int bitRate) {
|
public void setBitRate(int bitRate) {
|
||||||
this.bitRate = bitRate;
|
this.bitRate = bitRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isTunnelForward() {
|
||||||
|
return tunnelForward;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTunnelForward(boolean tunnelForward) {
|
||||||
|
this.tunnelForward = tunnelForward;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ public final class Server {
|
||||||
|
|
||||||
private static void scrcpy(Options options) throws IOException {
|
private static void scrcpy(Options options) throws IOException {
|
||||||
final Device device = new Device(options);
|
final Device device = new Device(options);
|
||||||
try (DesktopConnection connection = DesktopConnection.open(device)) {
|
boolean tunnelForward = options.isTunnelForward();
|
||||||
|
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
|
||||||
ScreenEncoder screenEncoder = new ScreenEncoder(options.getBitRate());
|
ScreenEncoder screenEncoder = new ScreenEncoder(options.getBitRate());
|
||||||
|
|
||||||
// asynchronous
|
// asynchronous
|
||||||
|
@ -55,6 +56,13 @@ public final class Server {
|
||||||
int bitRate = Integer.parseInt(args[1]);
|
int bitRate = Integer.parseInt(args[1]);
|
||||||
options.setBitRate(bitRate);
|
options.setBitRate(bitRate);
|
||||||
|
|
||||||
|
if (args.length < 3) {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
// use "adb forward" instead of "adb tunnel"? (so the server must listen)
|
||||||
|
boolean tunnelForward = Boolean.parseBoolean(args[2]);
|
||||||
|
options.setTunnelForward(tunnelForward);
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue