Merge branch 'dev' into release
This commit is contained in:
commit
f7bc0bd5b5
22 changed files with 332 additions and 91 deletions
31
README.md
31
README.md
|
@ -243,20 +243,23 @@ To run without installing:
|
||||||
|
|
||||||
## Shortcuts
|
## Shortcuts
|
||||||
|
|
||||||
| Action | Shortcut |
|
| Action | Shortcut |
|
||||||
| ------------------------------------- | -------------:|
|
| -------------------------------------- |:---------------------------- |
|
||||||
| switch fullscreen mode | `Ctrl`+`f` |
|
| switch fullscreen mode | `Ctrl`+`f` |
|
||||||
| resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` |
|
| resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` |
|
||||||
| resize window to remove black borders | `Ctrl`+`x` |
|
| resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ |
|
||||||
| click on `HOME` | `Ctrl`+`h` |
|
| click on `HOME` | `Ctrl`+`h` \| _Middle-click_ |
|
||||||
| click on `BACK` | `Ctrl`+`b` |
|
| click on `BACK` | `Ctrl`+`b` \| _Right-click²_ |
|
||||||
| click on `APP_SWITCH` | `Ctrl`+`m` |
|
| click on `APP_SWITCH` | `Ctrl`+`m` |
|
||||||
| click on `VOLUME_UP` | `Ctrl`+`+` |
|
| click on `VOLUME_UP` | `Ctrl`+`+` |
|
||||||
| click on `VOLUME_DOWN` | `Ctrl`+`-` |
|
| click on `VOLUME_DOWN` | `Ctrl`+`-` |
|
||||||
| click on `POWER` | `Ctrl`+`p` |
|
| click on `POWER` | `Ctrl`+`p` |
|
||||||
| turn screen on | _Right-click_ |
|
| turn screen on | _Right-click¹_ |
|
||||||
| paste computer clipboard to device | `Ctrl`+`v` |
|
| paste computer clipboard to device | `Ctrl`+`v` |
|
||||||
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
|
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
|
||||||
|
|
||||||
|
_¹Double-click on black borders to remove them._
|
||||||
|
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
||||||
|
|
||||||
|
|
||||||
## Why _scrcpy_?
|
## Why _scrcpy_?
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -39,9 +39,9 @@ int control_event_serialize(const struct control_event *event, unsigned char *bu
|
||||||
// injecting a text takes time, so limit the text length
|
// injecting a text takes time, so limit the text length
|
||||||
len = TEXT_MAX_LENGTH;
|
len = TEXT_MAX_LENGTH;
|
||||||
}
|
}
|
||||||
buf[1] = (Uint8) len;
|
write16(&buf[1], (Uint16) len);
|
||||||
memcpy(&buf[2], event->text_event.text, len);
|
memcpy(&buf[3], event->text_event.text, len);
|
||||||
return 2 + len;
|
return 3 + len;
|
||||||
}
|
}
|
||||||
case CONTROL_EVENT_TYPE_MOUSE:
|
case CONTROL_EVENT_TYPE_MOUSE:
|
||||||
buf[1] = event->mouse_event.action;
|
buf[1] = event->mouse_event.action;
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#define CONTROL_EVENT_QUEUE_SIZE 64
|
#define CONTROL_EVENT_QUEUE_SIZE 64
|
||||||
#define SERIALIZED_EVENT_MAX_SIZE 33
|
#define TEXT_MAX_LENGTH 300
|
||||||
#define TEXT_MAX_LENGTH 256
|
#define SERIALIZED_EVENT_MAX_SIZE 3 + TEXT_MAX_LENGTH
|
||||||
|
|
||||||
enum control_event_type {
|
enum control_event_type {
|
||||||
CONTROL_EVENT_TYPE_KEYCODE,
|
CONTROL_EVENT_TYPE_KEYCODE,
|
||||||
|
@ -20,7 +20,7 @@ enum control_event_type {
|
||||||
CONTROL_EVENT_TYPE_COMMAND,
|
CONTROL_EVENT_TYPE_COMMAND,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define CONTROL_EVENT_COMMAND_SCREEN_ON 0
|
#define CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON 0
|
||||||
|
|
||||||
struct control_event {
|
struct control_event {
|
||||||
enum control_event_type type;
|
enum control_event_type type;
|
||||||
|
|
|
@ -78,10 +78,11 @@ static inline void action_volume_down(struct controller *controller) {
|
||||||
send_keycode(controller, AKEYCODE_VOLUME_DOWN, "VOLUME_DOWN");
|
send_keycode(controller, AKEYCODE_VOLUME_DOWN, "VOLUME_DOWN");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void turn_screen_on(struct controller *controller) {
|
// turn the screen on if it was off, press BACK otherwise
|
||||||
|
static void press_back_or_turn_screen_on(struct controller *controller) {
|
||||||
struct control_event control_event;
|
struct control_event control_event;
|
||||||
control_event.type = CONTROL_EVENT_TYPE_COMMAND;
|
control_event.type = CONTROL_EVENT_TYPE_COMMAND;
|
||||||
control_event.command_event.action = CONTROL_EVENT_COMMAND_SCREEN_ON;
|
control_event.command_event.action = CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON;
|
||||||
|
|
||||||
if (!controller_push_event(controller, &control_event)) {
|
if (!controller_push_event(controller, &control_event)) {
|
||||||
LOGW("Cannot turn screen on");
|
LOGW("Cannot turn screen on");
|
||||||
|
@ -225,9 +226,25 @@ void input_manager_process_mouse_motion(struct input_manager *input_manager,
|
||||||
|
|
||||||
void input_manager_process_mouse_button(struct input_manager *input_manager,
|
void input_manager_process_mouse_button(struct input_manager *input_manager,
|
||||||
const SDL_MouseButtonEvent *event) {
|
const SDL_MouseButtonEvent *event) {
|
||||||
if (event->button == SDL_BUTTON_RIGHT && event->type == SDL_MOUSEBUTTONDOWN) {
|
if (event->type == SDL_MOUSEBUTTONDOWN) {
|
||||||
turn_screen_on(input_manager->controller);
|
if (event->button == SDL_BUTTON_RIGHT) {
|
||||||
return;
|
press_back_or_turn_screen_on(input_manager->controller);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event->button == SDL_BUTTON_MIDDLE) {
|
||||||
|
action_home(input_manager->controller);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// double-click on black borders resize to fit the device screen
|
||||||
|
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
|
||||||
|
SDL_bool outside_device_screen =
|
||||||
|
event->x < 0 || event->x >= input_manager->screen->frame_size.width ||
|
||||||
|
event->y < 0 || event->y >= input_manager->screen->frame_size.height;
|
||||||
|
if (outside_device_screen) {
|
||||||
|
screen_resize_to_fit(input_manager->screen);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
struct control_event control_event;
|
struct control_event control_event;
|
||||||
if (mouse_button_from_sdl_to_android(event, input_manager->screen->frame_size, &control_event)) {
|
if (mouse_button_from_sdl_to_android(event, input_manager->screen->frame_size, &control_event)) {
|
||||||
|
|
|
@ -57,14 +57,17 @@ static void usage(const char *arg0) {
|
||||||
" resize window to 1:1 (pixel-perfect)\n"
|
" resize window to 1:1 (pixel-perfect)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Ctrl+x\n"
|
" Ctrl+x\n"
|
||||||
|
" Double-click on black borders\n"
|
||||||
" resize window to remove black borders\n"
|
" resize window to remove black borders\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Ctrl+h\n"
|
" Ctrl+h\n"
|
||||||
" Home\n"
|
" Home\n"
|
||||||
|
" Middle-click\n"
|
||||||
" click on HOME\n"
|
" click on HOME\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Ctrl+b\n"
|
" Ctrl+b\n"
|
||||||
" Ctrl+Backspace\n"
|
" Ctrl+Backspace\n"
|
||||||
|
" Right-click (when screen is on)\n"
|
||||||
" click on BACK\n"
|
" click on BACK\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Ctrl+m\n"
|
" Ctrl+m\n"
|
||||||
|
@ -79,7 +82,7 @@ static void usage(const char *arg0) {
|
||||||
" Ctrl+p\n"
|
" Ctrl+p\n"
|
||||||
" click on POWER (turn screen on/off)\n"
|
" click on POWER (turn screen on/off)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Right-click\n"
|
" Right-click (when screen is off)\n"
|
||||||
" turn screen on\n"
|
" turn screen on\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Ctrl+v\n"
|
" Ctrl+v\n"
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,29 @@ static struct input_manager input_manager = {
|
||||||
.screen = &screen,
|
.screen = &screen,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if defined(__APPLE__) || defined(__WINDOWS__)
|
||||||
|
# define CONTINUOUS_RESIZING_WORKAROUND
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
||||||
|
// On Windows and MacOS, resizing blocks the event loop, so resizing events are
|
||||||
|
// not triggered. As a workaround, handle them in an event handler.
|
||||||
|
//
|
||||||
|
// <https://bugzilla.libsdl.org/show_bug.cgi?id=2077>
|
||||||
|
// <https://stackoverflow.com/a/40693139/1987178>
|
||||||
|
static int event_watcher(void *data, SDL_Event *event) {
|
||||||
|
if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||||
|
// called from another thread, not very safe, but it's a workaround!
|
||||||
|
screen_render(&screen);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static void event_loop(void) {
|
static void event_loop(void) {
|
||||||
|
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
||||||
|
SDL_AddEventWatch(event_watcher, NULL);
|
||||||
|
#endif
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while (SDL_WaitEvent(&event)) {
|
while (SDL_WaitEvent(&event)) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
|
@ -103,9 +125,9 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 b
|
||||||
// managed by the event loop. This blocking call blocks the event loop, so
|
// managed by the event loop. This blocking call blocks the event loop, so
|
||||||
// timeout the connection not to block indefinitely in case of SIGTERM.
|
// timeout the connection not to block indefinitely in case of SIGTERM.
|
||||||
#define SERVER_CONNECT_TIMEOUT_MS 2000
|
#define SERVER_CONNECT_TIMEOUT_MS 2000
|
||||||
socket_t device_socket = server_connect_to(&server, serial, SERVER_CONNECT_TIMEOUT_MS);
|
socket_t device_socket = server_connect_to(&server, SERVER_CONNECT_TIMEOUT_MS);
|
||||||
if (device_socket == INVALID_SOCKET) {
|
if (device_socket == INVALID_SOCKET) {
|
||||||
server_stop(&server, serial);
|
server_stop(&server);
|
||||||
ret = SDL_FALSE;
|
ret = SDL_FALSE;
|
||||||
goto finally_destroy_server;
|
goto finally_destroy_server;
|
||||||
}
|
}
|
||||||
|
@ -117,13 +139,13 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 b
|
||||||
// therefore, we transmit the screen size before the video stream, to be able
|
// therefore, we transmit the screen size before the video stream, to be able
|
||||||
// to init the window immediately
|
// to init the window immediately
|
||||||
if (!device_read_info(device_socket, device_name, &frame_size)) {
|
if (!device_read_info(device_socket, device_name, &frame_size)) {
|
||||||
server_stop(&server, serial);
|
server_stop(&server);
|
||||||
ret = SDL_FALSE;
|
ret = SDL_FALSE;
|
||||||
goto finally_destroy_server;
|
goto finally_destroy_server;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!frames_init(&frames)) {
|
if (!frames_init(&frames)) {
|
||||||
server_stop(&server, serial);
|
server_stop(&server);
|
||||||
ret = SDL_FALSE;
|
ret = SDL_FALSE;
|
||||||
goto finally_destroy_server;
|
goto finally_destroy_server;
|
||||||
}
|
}
|
||||||
|
@ -134,7 +156,7 @@ SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 b
|
||||||
// start the decoder
|
// start the decoder
|
||||||
if (!decoder_start(&decoder)) {
|
if (!decoder_start(&decoder)) {
|
||||||
ret = SDL_FALSE;
|
ret = SDL_FALSE;
|
||||||
server_stop(&server, serial);
|
server_stop(&server);
|
||||||
goto finally_destroy_frames;
|
goto finally_destroy_frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +187,7 @@ finally_destroy_controller:
|
||||||
finally_stop_decoder:
|
finally_stop_decoder:
|
||||||
decoder_stop(&decoder);
|
decoder_stop(&decoder);
|
||||||
// stop the server before decoder_join() to wake up the decoder
|
// stop the server before decoder_join() to wake up the decoder
|
||||||
server_stop(&server, serial);
|
server_stop(&server);
|
||||||
decoder_join(&decoder);
|
decoder_join(&decoder);
|
||||||
finally_destroy_frames:
|
finally_destroy_frames:
|
||||||
frames_destroy(&frames);
|
frames_destroy(&frames);
|
||||||
|
|
|
@ -18,8 +18,8 @@ SDL_bool sdl_init_and_configure(void) {
|
||||||
|
|
||||||
atexit(SDL_Quit);
|
atexit(SDL_Quit);
|
||||||
|
|
||||||
// Bilinear resizing
|
// Use the best available scale quality
|
||||||
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) {
|
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2")) {
|
||||||
LOGW("Could not enable bilinear filtering");
|
LOGW("Could not enable bilinear filtering");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
144
app/src/server.c
144
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,63 +147,84 @@ 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) {
|
||||||
|
server->serial = SDL_strdup(serial);
|
||||||
|
}
|
||||||
|
|
||||||
if (!push_server(serial)) {
|
if (!push_server(serial)) {
|
||||||
return SDL_FALSE;
|
return SDL_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
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, const char *serial, 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(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(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;
|
||||||
}
|
}
|
||||||
|
|
||||||
void server_stop(struct server *server, const char *serial) {
|
void server_stop(struct server *server) {
|
||||||
SDL_assert(server->process != PROCESS_NONE);
|
SDL_assert(server->process != PROCESS_NONE);
|
||||||
|
|
||||||
if (!cmd_terminate(server->process)) {
|
if (!cmd_terminate(server->process)) {
|
||||||
|
@ -151,13 +234,13 @@ void server_stop(struct server *server, const char *serial) {
|
||||||
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(serial);
|
disable_tunnel(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (server->server_copied_to_device) {
|
if (server->server_copied_to_device) {
|
||||||
remove_server(serial); // ignore failure
|
remove_server(server->serial); // ignore failure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,4 +251,5 @@ void server_destroy(struct server *server) {
|
||||||
if (server->device_socket != INVALID_SOCKET) {
|
if (server->device_socket != INVALID_SOCKET) {
|
||||||
close_socket(&server->device_socket);
|
close_socket(&server->device_socket);
|
||||||
}
|
}
|
||||||
|
SDL_free((void *) server->serial);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,24 @@
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
|
||||||
struct server {
|
struct server {
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SERVER_INITIALIZER { \
|
#define SERVER_INITIALIZER { \
|
||||||
|
.serial = NULL, \
|
||||||
.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, \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,10 +34,10 @@ SDL_bool server_start(struct server *server, const char *serial, Uint16 local_po
|
||||||
Uint16 max_size, Uint32 bit_rate);
|
Uint16 max_size, Uint32 bit_rate);
|
||||||
|
|
||||||
// block until the communication with the server is established
|
// block until the communication with the server is established
|
||||||
socket_t server_connect_to(struct server *server, const char *serial, Uint32 timeout_ms);
|
socket_t server_connect_to(struct server *server, Uint32 timeout_ms);
|
||||||
|
|
||||||
// disconnect and kill the server process
|
// disconnect and kill the server process
|
||||||
void server_stop(struct server *server, const char *serial);
|
void server_stop(struct server *server);
|
||||||
|
|
||||||
// close and release sockets
|
// close and release sockets
|
||||||
void server_destroy(struct server *server);
|
void server_destroy(struct server *server);
|
||||||
|
|
|
@ -35,16 +35,36 @@ static void test_serialize_text_event() {
|
||||||
|
|
||||||
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
|
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
|
||||||
int size = control_event_serialize(&event, buf);
|
int size = control_event_serialize(&event, buf);
|
||||||
assert(size == 15);
|
assert(size == 16);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
0x01, // CONTROL_EVENT_TYPE_KEYCODE
|
0x01, // CONTROL_EVENT_TYPE_KEYCODE
|
||||||
0x0d, // text length
|
0x00, 0x0d, // text length
|
||||||
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_serialize_long_text_event() {
|
||||||
|
struct control_event event;
|
||||||
|
event.type = CONTROL_EVENT_TYPE_TEXT;
|
||||||
|
char text[TEXT_MAX_LENGTH];
|
||||||
|
memset(text, 'a', sizeof(text));
|
||||||
|
event.text_event.text = text;
|
||||||
|
|
||||||
|
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
|
||||||
|
int size = control_event_serialize(&event, buf);
|
||||||
|
assert(size == 3 + sizeof(text));
|
||||||
|
|
||||||
|
unsigned char expected[3 + TEXT_MAX_LENGTH];
|
||||||
|
expected[0] = 0x01; // CONTROL_EVENT_TYPE_KEYCODE
|
||||||
|
expected[1] = 0x01;
|
||||||
|
expected[2] = 0x2c; // text length (16 bits)
|
||||||
|
memset(&expected[3], 'a', TEXT_MAX_LENGTH);
|
||||||
|
|
||||||
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
}
|
||||||
|
|
||||||
static void test_serialize_mouse_event() {
|
static void test_serialize_mouse_event() {
|
||||||
struct control_event event = {
|
struct control_event event = {
|
||||||
.type = CONTROL_EVENT_TYPE_MOUSE,
|
.type = CONTROL_EVENT_TYPE_MOUSE,
|
||||||
|
@ -114,6 +134,7 @@ static void test_serialize_scroll_event() {
|
||||||
int main() {
|
int main() {
|
||||||
test_serialize_keycode_event();
|
test_serialize_keycode_event();
|
||||||
test_serialize_text_event();
|
test_serialize_text_event();
|
||||||
|
test_serialize_long_text_event();
|
||||||
test_serialize_mouse_event();
|
test_serialize_mouse_event();
|
||||||
test_serialize_scroll_event();
|
test_serialize_scroll_event();
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -11,7 +11,7 @@ public final class ControlEvent {
|
||||||
public static final int TYPE_SCROLL = 3;
|
public static final int TYPE_SCROLL = 3;
|
||||||
public static final int TYPE_COMMAND = 4;
|
public static final int TYPE_COMMAND = 4;
|
||||||
|
|
||||||
public static final int COMMAND_SCREEN_ON = 0;
|
public static final int COMMAND_BACK_OR_SCREEN_ON = 0;
|
||||||
|
|
||||||
private int type;
|
private int type;
|
||||||
private String text;
|
private String text;
|
||||||
|
|
|
@ -13,8 +13,8 @@ public class ControlEventReader {
|
||||||
private static final int SCROLL_PAYLOAD_LENGTH = 16;
|
private static final int SCROLL_PAYLOAD_LENGTH = 16;
|
||||||
private static final int COMMAND_PAYLOAD_LENGTH = 1;
|
private static final int COMMAND_PAYLOAD_LENGTH = 1;
|
||||||
|
|
||||||
private static final int TEXT_MAX_LENGTH = 256;
|
public static final int TEXT_MAX_LENGTH = 300;
|
||||||
private static final int RAW_BUFFER_SIZE = 128;
|
private static final int RAW_BUFFER_SIZE = 1024;
|
||||||
|
|
||||||
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
|
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
|
||||||
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
||||||
|
@ -94,7 +94,7 @@ public class ControlEventReader {
|
||||||
if (buffer.remaining() < 1) {
|
if (buffer.remaining() < 1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int len = toUnsigned(buffer.get());
|
int len = toUnsigned(buffer.getShort());
|
||||||
if (buffer.remaining() < len) {
|
if (buffer.remaining() < len) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,24 @@ 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);
|
||||||
|
try {
|
||||||
|
return localServerSocket.accept();
|
||||||
|
} finally {
|
||||||
|
localServerSocket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
|
@ -50,8 +50,8 @@ public final class Device {
|
||||||
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo();
|
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo();
|
||||||
boolean rotated = (displayInfo.getRotation() & 1) != 0;
|
boolean rotated = (displayInfo.getRotation() & 1) != 0;
|
||||||
Size deviceSize = displayInfo.getSize();
|
Size deviceSize = displayInfo.getSize();
|
||||||
int w = deviceSize.getWidth();
|
int w = deviceSize.getWidth() & ~7; // in case it's not a multiple of 8
|
||||||
int h = deviceSize.getHeight();
|
int h = deviceSize.getHeight() & ~7;
|
||||||
if (maxSize > 0) {
|
if (maxSize > 0) {
|
||||||
if (BuildConfig.DEBUG && maxSize % 8 != 0) {
|
if (BuildConfig.DEBUG && maxSize % 8 != 0) {
|
||||||
throw new AssertionError("Max size must be a multiple of 8");
|
throw new AssertionError("Max size must be a multiple of 8");
|
||||||
|
|
|
@ -39,10 +39,6 @@ public class EventController {
|
||||||
coords.orientation = 0;
|
coords.orientation = 0;
|
||||||
coords.pressure = 1;
|
coords.pressure = 1;
|
||||||
coords.size = 1;
|
coords.size = 1;
|
||||||
coords.toolMajor = 1;
|
|
||||||
coords.toolMinor = 1;
|
|
||||||
coords.touchMajor = 1;
|
|
||||||
coords.touchMinor = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPointerCoords(Point point) {
|
private void setPointerCoords(Point point) {
|
||||||
|
@ -167,10 +163,15 @@ public class EventController {
|
||||||
return device.isScreenOn() || injectKeycode(KeyEvent.KEYCODE_POWER);
|
return device.isScreenOn() || injectKeycode(KeyEvent.KEYCODE_POWER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean pressBackOrTurnScreenOn() {
|
||||||
|
int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER;
|
||||||
|
return injectKeycode(keycode);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean executeCommand(int action) {
|
private boolean executeCommand(int action) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case ControlEvent.COMMAND_SCREEN_ON:
|
case ControlEvent.COMMAND_BACK_OR_SCREEN_ON:
|
||||||
return turnScreenOn();
|
return pressBackOrTurnScreenOn();
|
||||||
default:
|
default:
|
||||||
Ln.w("Unsupported command: " + action);
|
Ln.w("Unsupported command: " + action);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,14 +3,15 @@ package com.genymobile.scrcpy;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
public class ControlEventReaderTest {
|
public class ControlEventReaderTest {
|
||||||
|
|
||||||
|
@ -43,8 +44,8 @@ public class ControlEventReaderTest {
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlEvent.TYPE_TEXT);
|
dos.writeByte(ControlEvent.TYPE_TEXT);
|
||||||
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
||||||
dos.writeByte(text.length);
|
dos.writeShort(text.length);
|
||||||
dos.write("testé".getBytes(StandardCharsets.UTF_8));
|
dos.write(text);
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
@ -54,6 +55,26 @@ public class ControlEventReaderTest {
|
||||||
Assert.assertEquals("testé", event.getText());
|
Assert.assertEquals("testé", event.getText());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseLongTextEvent() throws IOException {
|
||||||
|
ControlEventReader reader = new ControlEventReader();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
dos.writeByte(ControlEvent.TYPE_TEXT);
|
||||||
|
byte[] text = new byte[ControlEventReader.TEXT_MAX_LENGTH];
|
||||||
|
Arrays.fill(text, (byte) 'a');
|
||||||
|
dos.writeShort(text.length);
|
||||||
|
dos.write(text);
|
||||||
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
|
ControlEvent event = reader.next();
|
||||||
|
|
||||||
|
Assert.assertEquals(ControlEvent.TYPE_TEXT, event.getType());
|
||||||
|
Assert.assertEquals(new String(text, StandardCharsets.US_ASCII), event.getText());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseMouseEvent() throws IOException {
|
public void testParseMouseEvent() throws IOException {
|
||||||
ControlEventReader reader = new ControlEventReader();
|
ControlEventReader reader = new ControlEventReader();
|
||||||
|
|
Loading…
Reference in a new issue