Compare commits

..

1 Commits

Author SHA1 Message Date
Romain Vimont
5b56c57795 Screen off PoC 2019-03-15 20:24:28 +01:00
27 changed files with 144 additions and 276 deletions

View File

@@ -294,19 +294,6 @@ _¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._ _²Right-click turns the screen on if it was off, presses BACK otherwise._
## Custom paths
To use a specific _adb_ binary, configure its path in the environment variable
`ADB`:
ADB=/path/to/adb scrcpy
To override the path of the `scrcpy-server.jar` file (it can be [useful] on
Windows), configure its path in `SCRCPY_SERVER_PATH`.
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
## Why _scrcpy_? ## Why _scrcpy_?
A colleague challenged me to find a name as unpronounceable as [gnirehtet]. A colleague challenged me to find a name as unpronounceable as [gnirehtet].

View File

@@ -143,34 +143,18 @@ else
link_args = [] link_args = []
endif endif
executable('scrcpy', src, executable('scrcpy', src, dependencies: dependencies, include_directories: src_dir, install: true, c_args: c_args, link_args: link_args)
dependencies: dependencies,
include_directories: src_dir,
install: true,
c_args: c_args,
link_args: link_args)
### TESTS ### TESTS
tests = [ tests = [
['test_control_event_queue', [ ['test_control_event_queue', ['tests/test_control_event_queue.c', 'src/control_event.c']],
'tests/test_control_event_queue.c', ['test_control_event_serialize', ['tests/test_control_event_serialize.c', 'src/control_event.c']],
'src/control_event.c' ['test_strutil', ['tests/test_strutil.c', 'src/str_util.c']],
]],
['test_control_event_serialize', [
'tests/test_control_event_serialize.c',
'src/control_event.c'
]],
['test_strutil', [
'tests/test_strutil.c',
'src/str_util.c'
]],
] ]
foreach t : tests foreach t : tests
exe = executable(t[0], t[1], exe = executable(t[0], t[1], include_directories: src_dir, dependencies: dependencies)
include_directories: src_dir,
dependencies: dependencies)
test(t[0], exe) test(t[0], exe)
endforeach endforeach

View File

@@ -1,6 +1,5 @@
#include "command.h" #include "command.h"
#include <assert.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -21,52 +20,15 @@ get_adb_command(void) {
return adb_command; return adb_command;
} }
// serialize argv to string "[arg1], [arg2], [arg3]"
static size_t
argv_to_string(const char *const *argv, char *buf, size_t bufsize) {
size_t idx = 0;
bool first = true;
while (*argv) {
const char *arg = *argv;
size_t len = strlen(arg);
// count space for "[], ...\0"
if (idx + len + 8 >= bufsize) {
// not enough space, truncate
assert(idx < bufsize - 4);
memcpy(&buf[idx], "...", 3);
idx += 3;
break;
}
if (first) {
first = false;
} else {
buf[idx++] = ',';
buf[idx++] = ' ';
}
buf[idx++] = '[';
memcpy(&buf[idx], arg, len);
idx += len;
buf[idx++] = ']';
argv++;
}
assert(idx < bufsize);
buf[idx] = '\0';
return idx;
}
static void static void
show_adb_err_msg(enum process_result err, const char *const argv[]) { show_adb_err_msg(enum process_result err) {
char buf[512];
switch (err) { switch (err) {
case PROCESS_ERROR_GENERIC: case PROCESS_ERROR_GENERIC:
argv_to_string(argv, buf, sizeof(buf)); LOGE("Failed to execute adb");
LOGE("Failed to execute: %s", buf);
break; break;
case PROCESS_ERROR_MISSING_BINARY: case PROCESS_ERROR_MISSING_BINARY:
argv_to_string(argv, buf, sizeof(buf)); LOGE("'adb' command not found (make it accessible from your PATH "
LOGE("Command not found: %s", buf); "or define its full path in the ADB environment variable)");
LOGE("(make 'adb' accessible from your PATH or define its full"
"path in the ADB environment variable)");
break; break;
case PROCESS_SUCCESS: case PROCESS_SUCCESS:
/* do nothing */ /* do nothing */
@@ -75,7 +37,7 @@ show_adb_err_msg(enum process_result err, const char *const argv[]) {
} }
process_t process_t
adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { adb_execute(const char *serial, const char *const adb_cmd[], int len) {
const char *cmd[len + 4]; const char *cmd[len + 4];
int i; int i;
process_t process; process_t process;
@@ -92,7 +54,7 @@ adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
cmd[len + i] = NULL; cmd[len + i] = NULL;
enum process_result r = cmd_execute(cmd[0], cmd, &process); enum process_result r = cmd_execute(cmd[0], cmd, &process);
if (r != PROCESS_SUCCESS) { if (r != PROCESS_SUCCESS) {
show_adb_err_msg(r, cmd); show_adb_err_msg(r);
return PROCESS_NONE; return PROCESS_NONE;
} }
return process; return process;

View File

@@ -49,7 +49,7 @@ bool
cmd_simple_wait(process_t pid, exit_code_t *exit_code); cmd_simple_wait(process_t pid, exit_code_t *exit_code);
process_t process_t
adb_execute(const char *serial, const char *const adb_cmd[], size_t len); adb_execute(const char *serial, const char *const adb_cmd[], int len);
process_t process_t
adb_forward(const char *serial, uint16_t local_port, adb_forward(const char *serial, uint16_t local_port,

View File

@@ -43,9 +43,4 @@
# define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP # define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
#endif #endif
#if SDL_VERSION_ATLEAST(2, 0, 8)
// <https://hg.libsdl.org/SDL/rev/dfde5d3f9781>
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
#endif
#endif #endif

View File

@@ -81,9 +81,9 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) {
MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE); MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE);
MAP(SDLK_BACKSPACE, AKEYCODE_DEL); MAP(SDLK_BACKSPACE, AKEYCODE_DEL);
MAP(SDLK_TAB, AKEYCODE_TAB); MAP(SDLK_TAB, AKEYCODE_TAB);
MAP(SDLK_HOME, AKEYCODE_HOME);
MAP(SDLK_PAGEUP, AKEYCODE_PAGE_UP); MAP(SDLK_PAGEUP, AKEYCODE_PAGE_UP);
MAP(SDLK_DELETE, AKEYCODE_FORWARD_DEL); MAP(SDLK_DELETE, AKEYCODE_FORWARD_DEL);
MAP(SDLK_HOME, AKEYCODE_MOVE_HOME);
MAP(SDLK_END, AKEYCODE_MOVE_END); MAP(SDLK_END, AKEYCODE_MOVE_END);
MAP(SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN); MAP(SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN);
MAP(SDLK_RIGHT, AKEYCODE_DPAD_RIGHT); MAP(SDLK_RIGHT, AKEYCODE_DPAD_RIGHT);

View File

@@ -10,11 +10,11 @@
struct request { struct request {
file_handler_action_t action; file_handler_action_t action;
char *file; const char *file;
}; };
static struct request * static struct request *
request_new(file_handler_action_t action, char *file) { request_new(file_handler_action_t action, const char *file) {
struct request *req = SDL_malloc(sizeof(*req)); struct request *req = SDL_malloc(sizeof(*req));
if (!req) { if (!req) {
return NULL; return NULL;
@@ -29,8 +29,8 @@ request_free(struct request *req) {
if (!req) { if (!req) {
return; return;
} }
SDL_free(req->file); SDL_free((void *) req->file);
SDL_free(req); SDL_free((void *) req);
} }
static bool static bool
@@ -121,7 +121,7 @@ file_handler_destroy(struct file_handler *file_handler) {
SDL_DestroyCond(file_handler->event_cond); SDL_DestroyCond(file_handler->event_cond);
SDL_DestroyMutex(file_handler->mutex); SDL_DestroyMutex(file_handler->mutex);
request_queue_destroy(&file_handler->queue); request_queue_destroy(&file_handler->queue);
SDL_free(file_handler->serial); SDL_free((void *) file_handler->serial);
} }
static process_t static process_t
@@ -137,7 +137,7 @@ push_file(const char *serial, const char *file) {
bool bool
file_handler_request(struct file_handler *file_handler, file_handler_request(struct file_handler *file_handler,
file_handler_action_t action, file_handler_action_t action,
char *file) { const char *file) {
bool res; bool res;
// start file_handler if it's used for the first time // start file_handler if it's used for the first time

View File

@@ -21,7 +21,7 @@ struct request_queue {
}; };
struct file_handler { struct file_handler {
char *serial; const char *serial;
SDL_Thread *thread; SDL_Thread *thread;
SDL_mutex *mutex; SDL_mutex *mutex;
SDL_cond *event_cond; SDL_cond *event_cond;
@@ -46,10 +46,9 @@ file_handler_stop(struct file_handler *file_handler);
void void
file_handler_join(struct file_handler *file_handler); file_handler_join(struct file_handler *file_handler);
// take ownership of file, and will SDL_free() it
bool bool
file_handler_request(struct file_handler *file_handler, file_handler_request(struct file_handler *file_handler,
file_handler_action_t action, file_handler_action_t action,
char *file); const char *file);
#endif #endif

View File

@@ -5,7 +5,6 @@
#include <stdint.h> #include <stdint.h>
#include <unistd.h> #include <unistd.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "compat.h" #include "compat.h"
@@ -105,6 +104,7 @@ static void usage(const char *arg0) {
" resize window to remove black borders\n" " resize window to remove black borders\n"
"\n" "\n"
" Ctrl+h\n" " Ctrl+h\n"
" Home\n"
" Middle-click\n" " Middle-click\n"
" click on HOME\n" " click on HOME\n"
"\n" "\n"

View File

@@ -9,7 +9,6 @@
#include "command.h" #include "command.h"
#include "common.h" #include "common.h"
#include "compat.h"
#include "controller.h" #include "controller.h"
#include "decoder.h" #include "decoder.h"
#include "device.h" #include "device.h"
@@ -69,13 +68,6 @@ sdl_init_and_configure(bool display) {
} }
#endif #endif
#ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
// Disable compositor bypassing on X11
if (!SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0")) {
LOGW("Could not disable X11 compositor bypass");
}
#endif
// Do not disable the screensaver when scrcpy is running // Do not disable the screensaver when scrcpy is running
SDL_EnableScreenSaver(); SDL_EnableScreenSaver();
@@ -290,7 +282,8 @@ scrcpy(const struct scrcpy_options *options) {
goto finally_destroy_server; goto finally_destroy_server;
} }
if (!server_connect_to(&server)) { socket_t device_socket = server_connect_to(&server);
if (device_socket == INVALID_SOCKET) {
server_stop(&server); server_stop(&server);
ret = false; ret = false;
goto finally_destroy_server; goto finally_destroy_server;
@@ -302,7 +295,7 @@ scrcpy(const struct scrcpy_options *options) {
// screenrecord does not send frames when the screen content does not // screenrecord does not send frames when the screen content does not
// change therefore, we transmit the screen size before the video stream, // change therefore, we transmit the screen size before the video stream,
// to be able to init the window immediately // to be able to init the window immediately
if (!device_read_info(server.video_socket, device_name, &frame_size)) { if (!device_read_info(device_socket, device_name, &frame_size)) {
server_stop(&server); server_stop(&server);
ret = false; ret = false;
goto finally_destroy_server; goto finally_destroy_server;
@@ -341,7 +334,7 @@ scrcpy(const struct scrcpy_options *options) {
av_log_set_callback(av_log_callback); av_log_set_callback(av_log_callback);
stream_init(&stream, server.video_socket, dec, rec); stream_init(&stream, device_socket, dec, rec);
// now we consumed the header values, the socket receives the video stream // now we consumed the header values, the socket receives the video stream
// start the stream // start the stream
@@ -353,7 +346,7 @@ scrcpy(const struct scrcpy_options *options) {
if (display) { if (display) {
if (control) { if (control) {
if (!controller_init(&controller, server.control_socket)) { if (!controller_init(&controller, device_socket)) {
ret = false; ret = false;
goto finally_stop_stream; goto finally_stop_stream;
} }

View File

@@ -177,12 +177,13 @@ screen_init_rendering(struct screen *screen, const char *device_name,
} }
SDL_Surface *icon = read_xpm(icon_xpm); SDL_Surface *icon = read_xpm(icon_xpm);
if (icon) { if (!icon) {
SDL_SetWindowIcon(screen->window, icon); LOGE("Could not load icon: %s", SDL_GetError());
SDL_FreeSurface(icon); screen_destroy(screen);
} else { return false;
LOGW("Could not load icon");
} }
SDL_SetWindowIcon(screen->window, icon);
SDL_FreeSurface(icon);
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width, LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width,
frame_size.height); frame_size.height);

View File

@@ -119,7 +119,7 @@ connect_and_read_byte(uint16_t port) {
char byte; char byte;
// the connection may succeed even if the server behind the "adb tunnel" // the connection may succeed even if the server behind the "adb tunnel"
// is not listening, so read one byte to detect a working connection // is not listening, so read one byte to detect a working connection
if (net_recv(socket, &byte, 1) != 1) { if (net_recv_all(socket, &byte, 1) != 1) {
// the server is not listening yet behind the adb tunnel // the server is not listening yet behind the adb tunnel
return INVALID_SOCKET; return INVALID_SOCKET;
} }
@@ -210,7 +210,7 @@ server_start(struct server *server, const char *serial,
close_socket(&server->server_socket); close_socket(&server->server_socket);
} }
disable_tunnel(server); disable_tunnel(server);
SDL_free(server->serial); SDL_free((void *) server->serial);
return false; return false;
} }
@@ -219,44 +219,31 @@ server_start(struct server *server, const char *serial,
return true; return true;
} }
bool socket_t
server_connect_to(struct server *server) { server_connect_to(struct server *server) {
if (!server->tunnel_forward) { if (!server->tunnel_forward) {
server->video_socket = net_accept(server->server_socket); server->device_socket = net_accept(server->server_socket);
if (server->video_socket == INVALID_SOCKET) {
return false;
}
server->control_socket = net_accept(server->server_socket);
if (server->control_socket == INVALID_SOCKET) {
// the video_socket will be clean up on destroy
return false;
}
// we don't need the server socket anymore
close_socket(&server->server_socket);
} else { } else {
uint32_t attempts = 100; uint32_t attempts = 100;
uint32_t delay = 100; // ms uint32_t delay = 100; // ms
server->video_socket = server->device_socket = connect_to_server(server->local_port, attempts,
connect_to_server(server->local_port, attempts, delay); delay);
if (server->video_socket == INVALID_SOCKET) { }
return false;
}
// we know that the device is listening, we don't need several attempts if (server->device_socket == INVALID_SOCKET) {
server->control_socket = return INVALID_SOCKET;
net_connect(IPV4_LOCALHOST, server->local_port); }
if (server->control_socket == INVALID_SOCKET) {
return false; if (!server->tunnel_forward) {
} // we don't need the server socket anymore
close_socket(&server->server_socket);
} }
// we don't need the adb tunnel anymore // we don't need the adb tunnel anymore
disable_tunnel(server); // ignore failure disable_tunnel(server); // ignore failure
server->tunnel_enabled = false; server->tunnel_enabled = false;
return true; return server->device_socket;
} }
void void
@@ -281,11 +268,8 @@ server_destroy(struct server *server) {
if (server->server_socket != INVALID_SOCKET) { if (server->server_socket != INVALID_SOCKET) {
close_socket(&server->server_socket); close_socket(&server->server_socket);
} }
if (server->video_socket != INVALID_SOCKET) { if (server->device_socket != INVALID_SOCKET) {
close_socket(&server->video_socket); close_socket(&server->device_socket);
}
if (server->control_socket != INVALID_SOCKET) {
close_socket(&server->control_socket);
} }
SDL_free(server->serial); SDL_free(server->serial);
} }

View File

@@ -11,21 +11,19 @@ struct server {
char *serial; char *serial;
process_t process; process_t process;
socket_t server_socket; // only used if !tunnel_forward socket_t server_socket; // only used if !tunnel_forward
socket_t video_socket; socket_t device_socket;
socket_t control_socket;
uint16_t local_port; uint16_t local_port;
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"
bool send_frame_meta; // request frame PTS to be able to record properly bool send_frame_meta; // request frame PTS to be able to record properly
}; };
#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, \ .device_socket = INVALID_SOCKET, \
.control_socket = INVALID_SOCKET, \ .local_port = 0, \
.local_port = 0, \
.tunnel_enabled = false, \ .tunnel_enabled = false, \
.tunnel_forward = false, \ .tunnel_forward = false, \
.send_frame_meta = false, \ .send_frame_meta = false, \
@@ -38,11 +36,11 @@ server_init(struct server *server);
// push, enable tunnel et start the server // push, enable tunnel et start the server
bool bool
server_start(struct server *server, const char *serial, server_start(struct server *server, const char *serial,
uint16_t local_port, uint16_t max_size, uint32_t bit_rate, uint16_t local_port, uint16_t max_size, uint32_t bit_rate,
const char *crop, bool send_frame_meta); const char *crop, bool send_frame_meta);
// block until the communication with the server is established // block until the communication with the server is established
bool socket_t
server_connect_to(struct server *server); server_connect_to(struct server *server);
// disconnect and kill the server process // disconnect and kill the server process

View File

@@ -105,10 +105,6 @@ read_xpm(char *xpm[]) {
width, height, width, height,
32, 4 * width, 32, 4 * width,
rmask, gmask, bmask, amask); rmask, gmask, bmask, amask);
if (!surface) {
LOGE("Could not create icon surface");
return NULL;
}
// make the surface own the raw pixels // make the surface own the raw pixels
surface->flags &= ~SDL_PREALLOC; surface->flags &= ~SDL_PREALLOC;
return surface; return surface;

View File

@@ -5,21 +5,21 @@
static void test_control_event_queue_empty(void) { static void test_control_event_queue_empty(void) {
struct control_event_queue queue; struct control_event_queue queue;
bool init_ok = control_event_queue_init(&queue); SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok); assert(init_ok);
assert(control_event_queue_is_empty(&queue)); assert(control_event_queue_is_empty(&queue));
struct control_event dummy_event; struct control_event dummy_event;
bool push_ok = control_event_queue_push(&queue, &dummy_event); SDL_bool push_ok = control_event_queue_push(&queue, &dummy_event);
assert(push_ok); assert(push_ok);
assert(!control_event_queue_is_empty(&queue)); assert(!control_event_queue_is_empty(&queue));
bool take_ok = control_event_queue_take(&queue, &dummy_event); SDL_bool take_ok = control_event_queue_take(&queue, &dummy_event);
assert(take_ok); assert(take_ok);
assert(control_event_queue_is_empty(&queue)); assert(control_event_queue_is_empty(&queue));
bool take_empty_ok = control_event_queue_take(&queue, &dummy_event); SDL_bool take_empty_ok = control_event_queue_take(&queue, &dummy_event);
assert(!take_empty_ok); // the queue is empty assert(!take_empty_ok); // the queue is empty
control_event_queue_destroy(&queue); control_event_queue_destroy(&queue);
@@ -27,7 +27,7 @@ static void test_control_event_queue_empty(void) {
static void test_control_event_queue_full(void) { static void test_control_event_queue_full(void) {
struct control_event_queue queue; struct control_event_queue queue;
bool init_ok = control_event_queue_init(&queue); SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok); assert(init_ok);
assert(!control_event_queue_is_full(&queue)); assert(!control_event_queue_is_full(&queue));
@@ -36,7 +36,7 @@ static void test_control_event_queue_full(void) {
// fill the queue // fill the queue
while (control_event_queue_push(&queue, &dummy_event)); while (control_event_queue_push(&queue, &dummy_event));
bool take_ok = control_event_queue_take(&queue, &dummy_event); SDL_bool take_ok = control_event_queue_take(&queue, &dummy_event);
assert(take_ok); assert(take_ok);
assert(!control_event_queue_is_full(&queue)); assert(!control_event_queue_is_full(&queue));
@@ -45,7 +45,7 @@ static void test_control_event_queue_full(void) {
static void test_control_event_queue_push_take(void) { static void test_control_event_queue_push_take(void) {
struct control_event_queue queue; struct control_event_queue queue;
bool init_ok = control_event_queue_init(&queue); SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok); assert(init_ok);
struct control_event event = { struct control_event event = {
@@ -57,7 +57,7 @@ static void test_control_event_queue_push_take(void) {
}, },
}; };
bool push1_ok = control_event_queue_push(&queue, &event); SDL_bool push1_ok = control_event_queue_push(&queue, &event);
assert(push1_ok); assert(push1_ok);
event = (struct control_event) { event = (struct control_event) {
@@ -67,11 +67,11 @@ static void test_control_event_queue_push_take(void) {
}, },
}; };
bool push2_ok = control_event_queue_push(&queue, &event); SDL_bool push2_ok = control_event_queue_push(&queue, &event);
assert(push2_ok); assert(push2_ok);
// overwrite event // overwrite event
bool take1_ok = control_event_queue_take(&queue, &event); SDL_bool take1_ok = control_event_queue_take(&queue, &event);
assert(take1_ok); assert(take1_ok);
assert(event.type == CONTROL_EVENT_TYPE_KEYCODE); assert(event.type == CONTROL_EVENT_TYPE_KEYCODE);
assert(event.keycode_event.action == AKEY_EVENT_ACTION_DOWN); assert(event.keycode_event.action == AKEY_EVENT_ACTION_DOWN);
@@ -79,7 +79,7 @@ static void test_control_event_queue_push_take(void) {
assert(event.keycode_event.metastate == (AMETA_CTRL_LEFT_ON | AMETA_CTRL_ON)); assert(event.keycode_event.metastate == (AMETA_CTRL_LEFT_ON | AMETA_CTRL_ON));
// overwrite event // overwrite event
bool take2_ok = control_event_queue_take(&queue, &event); SDL_bool take2_ok = control_event_queue_take(&queue, &event);
assert(take2_ok); assert(take2_ok);
assert(event.type == CONTROL_EVENT_TYPE_TEXT); assert(event.type == CONTROL_EVENT_TYPE_TEXT);
assert(!strcmp(event.text_event.text, "abc")); assert(!strcmp(event.text_event.text, "abc"));

View File

@@ -49,14 +49,13 @@ static void test_serialize_text_event(void) {
static void test_serialize_long_text_event(void) { static void test_serialize_long_text_event(void) {
struct control_event event; struct control_event event;
event.type = CONTROL_EVENT_TYPE_TEXT; event.type = CONTROL_EVENT_TYPE_TEXT;
char text[TEXT_MAX_LENGTH + 1]; char text[TEXT_MAX_LENGTH];
memset(text, 'a', sizeof(text)); memset(text, 'a', sizeof(text));
text[TEXT_MAX_LENGTH] = '\0';
event.text_event.text = text; event.text_event.text = text;
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 == 3 + TEXT_MAX_LENGTH); assert(size == 3 + sizeof(text));
unsigned char expected[3 + TEXT_MAX_LENGTH]; unsigned char expected[3 + TEXT_MAX_LENGTH];
expected[0] = 0x01; // CONTROL_EVENT_TYPE_KEYCODE expected[0] = 0x01; // CONTROL_EVENT_TYPE_KEYCODE

View File

@@ -7,7 +7,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.3.0' classpath 'com.android.tools.build:gradle:3.1.1'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files

View File

@@ -1,6 +1,6 @@
#Thu Apr 18 11:45:59 CEST 2019 #Mon Jun 04 11:48:32 CEST 2018
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

View File

@@ -1,22 +1,13 @@
#!/bin/bash #!/bin/bash
set -e set -e
# test locally # build and test locally
TESTDIR=build_test
rm -rf "$TESTDIR"
# run client tests with ASAN enabled
meson "$TESTDIR" -Db_sanitize=address
ninja -C"$TESTDIR" test
# test server
GRADLE=${GRADLE:-./gradlew}
$GRADLE -p server check
BUILDDIR=build_release BUILDDIR=build_release
rm -rf "$BUILDDIR" rm -rf "$BUILDDIR"
meson "$BUILDDIR" --buildtype release --strip -Db_lto=true meson "$BUILDDIR" --buildtype release --strip -Db_lto=true
cd "$BUILDDIR" cd "$BUILDDIR"
ninja ninja
ninja test
cd - cd -
# build Windows releases # build Windows releases

View File

@@ -16,20 +16,16 @@ public final class DesktopConnection implements Closeable {
private static final String SOCKET_NAME = "scrcpy"; private static final String SOCKET_NAME = "scrcpy";
private final LocalSocket videoSocket; private final LocalSocket socket;
private final FileDescriptor videoFd; private final InputStream inputStream;
private final FileDescriptor fd;
private final LocalSocket controlSocket;
private final InputStream controlInputStream;
private final ControlEventReader reader = new ControlEventReader(); private final ControlEventReader reader = new ControlEventReader();
private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException { private DesktopConnection(LocalSocket socket) throws IOException {
this.videoSocket = videoSocket; this.socket = socket;
this.controlSocket = controlSocket; inputStream = socket.getInputStream();
controlInputStream = controlSocket.getInputStream(); fd = socket.getFileDescriptor();
videoFd = videoSocket.getFileDescriptor();
} }
private static LocalSocket connect(String abstractName) throws IOException { private static LocalSocket connect(String abstractName) throws IOException {
@@ -48,46 +44,25 @@ public final class DesktopConnection implements Closeable {
} }
public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException { public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException {
LocalSocket videoSocket; LocalSocket socket;
LocalSocket controlSocket;
if (tunnelForward) { if (tunnelForward) {
LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME); socket = listenAndAccept(SOCKET_NAME);
try { // send one byte so the client may read() to detect a connection error
videoSocket = localServerSocket.accept(); socket.getOutputStream().write(0);
// send one byte so the client may read() to detect a connection error
videoSocket.getOutputStream().write(0);
try {
controlSocket = localServerSocket.accept();
} catch (IOException | RuntimeException e) {
videoSocket.close();
throw e;
}
} finally {
localServerSocket.close();
}
} else { } else {
videoSocket = connect(SOCKET_NAME); socket = connect(SOCKET_NAME);
try {
controlSocket = connect(SOCKET_NAME);
} catch (IOException | RuntimeException e) {
videoSocket.close();
throw e;
}
} }
DesktopConnection connection = new DesktopConnection(videoSocket, controlSocket); DesktopConnection connection = new DesktopConnection(socket);
Size videoSize = device.getScreenInfo().getVideoSize(); Size videoSize = device.getScreenInfo().getVideoSize();
connection.send(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight()); connection.send(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
return connection; return connection;
} }
public void close() throws IOException { public void close() throws IOException {
videoSocket.shutdownInput(); socket.shutdownInput();
videoSocket.shutdownOutput(); socket.shutdownOutput();
videoSocket.close(); socket.close();
controlSocket.shutdownInput();
controlSocket.shutdownOutput();
controlSocket.close();
} }
@SuppressWarnings("checkstyle:MagicNumber") @SuppressWarnings("checkstyle:MagicNumber")
@@ -103,17 +78,17 @@ public final class DesktopConnection implements Closeable {
buffer[DEVICE_NAME_FIELD_LENGTH + 1] = (byte) width; buffer[DEVICE_NAME_FIELD_LENGTH + 1] = (byte) width;
buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8); buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8);
buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height; buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height;
IO.writeFully(videoFd, buffer, 0, buffer.length); IO.writeFully(fd, buffer, 0, buffer.length);
} }
public FileDescriptor getVideoFd() { public FileDescriptor getFd() {
return videoFd; return fd;
} }
public ControlEvent receiveControlEvent() throws IOException { public ControlEvent receiveControlEvent() throws IOException {
ControlEvent event = reader.next(); ControlEvent event = reader.next();
while (event == null) { while (event == null) {
reader.readFrom(controlInputStream); reader.readFrom(inputStream);
event = reader.next(); event = reader.next();
} }
return event; return event;

View File

@@ -1,6 +1,7 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.graphics.Point; import android.graphics.Point;
import android.os.SystemClock; import android.os.SystemClock;
@@ -104,16 +105,13 @@ public class EventController {
return true; return true;
} }
private int injectText(String text) { private boolean injectText(String text) {
int successCount = 0;
for (char c : text.toCharArray()) { for (char c : text.toCharArray()) {
if (!injectChar(c)) { if (!injectChar(c)) {
Ln.w("Could not inject char u+" + String.format("%04x", (int) c)); return false;
continue;
} }
successCount++;
} }
return successCount; return true;
} }
private boolean injectMouse(int action, int buttons, Position position) { private boolean injectMouse(int action, int buttons, Position position) {

View File

@@ -8,7 +8,7 @@ import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
public final class IO { public class IO {
private IO() { private IO() {
// not instantiable // not instantiable
} }

View File

@@ -52,13 +52,7 @@ public final class Ln {
if (isEnabled(Level.ERROR)) { if (isEnabled(Level.ERROR)) {
Log.e(TAG, message, throwable); Log.e(TAG, message, throwable);
System.out.println("ERROR: " + message); System.out.println("ERROR: " + message);
if (throwable != null) { throwable.printStackTrace();
throwable.printStackTrace();
}
} }
} }
public static void e(String message) {
e(message, null);
}
} }

View File

@@ -3,6 +3,7 @@ package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.SurfaceControl; import com.genymobile.scrcpy.wrappers.SurfaceControl;
import android.graphics.Rect; import android.graphics.Rect;
import android.media.MediaMuxer;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCodecInfo; import android.media.MediaCodecInfo;
import android.media.MediaFormat; import android.media.MediaFormat;
@@ -56,6 +57,8 @@ public class ScreenEncoder implements Device.RotationListener {
public void streamScreen(Device device, FileDescriptor fd) throws IOException { public void streamScreen(Device device, FileDescriptor fd) throws IOException {
MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval); MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval);
device.setRotationListener(this); device.setRotationListener(this);
IBinder d = SurfaceControl.getBuiltInDisplay(0);
SurfaceControl.setDisplayPowerMode(d, SurfaceControl.POWER_MODE_OFF);
boolean alive; boolean alive;
try { try {
do { do {
@@ -70,9 +73,8 @@ public class ScreenEncoder implements Device.RotationListener {
codec.start(); codec.start();
try { try {
alive = encode(codec, fd); alive = encode(codec, fd);
// do not call stop() on exception, it would trigger an IllegalStateException
codec.stop();
} finally { } finally {
codec.stop();
destroyDisplay(display); destroyDisplay(display);
codec.release(); codec.release();
surface.release(); surface.release();

View File

@@ -4,6 +4,7 @@ import android.graphics.Rect;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
public final class Server { public final class Server {
@@ -24,7 +25,7 @@ public final class Server {
try { try {
// synchronous // synchronous
screenEncoder.streamScreen(device, connection.getVideoFd()); screenEncoder.streamScreen(device, connection.getFd());
} catch (IOException e) { } catch (IOException e) {
// this is expected on close // this is expected on close
Ln.d("Screen streaming stopped"); Ln.d("Screen streaming stopped");
@@ -48,9 +49,8 @@ public final class Server {
@SuppressWarnings("checkstyle:MagicNumber") @SuppressWarnings("checkstyle:MagicNumber")
private static Options createOptions(String... args) { private static Options createOptions(String... args) {
if (args.length != 5) { if (args.length != 5)
throw new IllegalArgumentException("Expecting 5 parameters"); throw new IllegalArgumentException("Expecting 5 parameters");
}
Options options = new Options(); Options options = new Options();
@@ -73,7 +73,6 @@ public final class Server {
return options; return options;
} }
@SuppressWarnings("checkstyle:MagicNumber")
private static Rect parseCrop(String crop) { private static Rect parseCrop(String crop) {
if ("-".equals(crop)) { if ("-".equals(crop)) {
return null; return null;

View File

@@ -1,8 +1,8 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import android.annotation.SuppressLint;
import android.os.IInterface; import android.os.IInterface;
import android.view.InputEvent;
import com.genymobile.scrcpy.Ln;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@@ -10,42 +10,32 @@ import java.lang.reflect.Method;
public class StatusBarManager { public class StatusBarManager {
private final IInterface manager; private final IInterface manager;
private Method expandNotificationsPanelMethod; private final Method expandNotificationsPanelMethod;
private Method collapsePanelsMethod; private final Method collapsePanelsMethod;
public StatusBarManager(IInterface manager) { public StatusBarManager(IInterface manager) {
this.manager = manager; this.manager = manager;
try {
expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
} }
public void expandNotificationsPanel() { public void expandNotificationsPanel() {
if (expandNotificationsPanelMethod == null) {
try {
expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
} catch (NoSuchMethodException e) {
Ln.e("ServiceBarManager.expandNotificationsPanel() is not available on this device");
return;
}
}
try { try {
expandNotificationsPanelMethod.invoke(manager); expandNotificationsPanelMethod.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) { } catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Cannot invoke ServiceBarManager.expandNotificationsPanel()", e); throw new AssertionError(e);
} }
} }
public void collapsePanels() { public void collapsePanels() {
if (collapsePanelsMethod == null) {
try {
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
} catch (NoSuchMethodException e) {
Ln.e("ServiceBarManager.collapsePanels() is not available on this device");
return;
}
}
try { try {
collapsePanelsMethod.invoke(manager); collapsePanelsMethod.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) { } catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Cannot invoke ServiceBarManager.collapsePanels()", e); throw new AssertionError(e);
} }
} }
} }

View File

@@ -10,6 +10,11 @@ public final class SurfaceControl {
private static final Class<?> CLASS; private static final Class<?> CLASS;
public static final int POWER_MODE_OFF = 0;
public static final int POWER_MODE_DOZE = 1;
public static final int POWER_MODE_NORMAL = 2;
public static final int POWER_MODE_DOZE_SUSPEND = 3;
static { static {
try { try {
CLASS = Class.forName("android.view.SurfaceControl"); CLASS = Class.forName("android.view.SurfaceControl");
@@ -71,6 +76,22 @@ public final class SurfaceControl {
} }
} }
public static IBinder getBuiltInDisplay(int builtInDisplayId) {
try {
return (IBinder) CLASS.getMethod("getBuiltInDisplay", int.class).invoke(null, builtInDisplayId);
} catch (Exception e) {
throw new AssertionError(e);
}
}
public static void setDisplayPowerMode(IBinder displayToken, int mode) {
try {
CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class).invoke(null, displayToken, mode);
} catch (Exception e) {
throw new AssertionError(e);
}
}
public static void destroyDisplay(IBinder displayToken) { public static void destroyDisplay(IBinder displayToken) {
try { try {
CLASS.getMethod("destroyDisplay", IBinder.class).invoke(null, displayToken); CLASS.getMethod("destroyDisplay", IBinder.class).invoke(null, displayToken);