Compare commits

..

33 Commits

Author SHA1 Message Date
Romain Vimont
72c83b2ac2 Format meson.build for readability 2019-05-28 21:04:03 +02:00
Romain Vimont
7dcac5b8b3 Replace SDL_bool by bool in tests
Commit dfed1b250e replaced SDL types by
standard types in sources, but tests were not updated.
2019-05-28 21:04:03 +02:00
Romain Vimont
f88345de0a Use different sockets for video and control
The socket used the device-to-computer direction to stream the video and
the computer-to-device direction to send control events.

Some features, like copy-paste from device to computer, require to send
non-video data from the device to the computer.

To make them possible, use two sockets:
 - one for streaming the video from the device to the client;
 - one for control/events in both direction.
2019-05-28 21:03:54 +02:00
Romain Vimont
2a1bdd0af3 Use net_recv() to read only one byte
Partial read is impossible for 1 byte, so net_recv_all() is useless.
2019-05-28 21:03:44 +02:00
Romain Vimont
2a7fee6c16 Simplify server_connect_to()
Only use 2 branches, using either forward or remote tunnel.
2019-05-28 21:02:57 +02:00
Romain Vimont
92f0266704 Make server_connect_to() return a bool
The resulting socket is accessible from the server instance, there is no
need to return it.

This paves the way to use several sockets in parallel.
2019-05-28 13:43:34 +02:00
Romain Vimont
6cf134f31f Fix indentation
Previous refactorings broke indentation.
2019-05-28 13:37:27 +02:00
Romain Vimont
b75f0e9427 Merge branch 'master' into dev 2019-05-28 13:31:37 +02:00
Romain Vimont
5d473efeb5 Bind Home key to MOVE_HOME
On pressing Home key on the computer, move the cursor to the beginning
of the line instead of going back to the home screen.

<https://developer.android.com/reference/android/view/KeyEvent.html#KEYCODE_HOME>
<https://developer.android.com/reference/android/view/KeyEvent.html#KEYCODE_MOVE_HOME>

Fixes (part of) <https://github.com/Genymobile/scrcpy/issues/555>.
2019-05-27 10:24:47 +02:00
Romain Vimont
a41dd6c79f Make owned filename a pointer-to-non-const
The file handler owns the filename string, so it needs to free it.
Therefore, it should not be a pointer-to-const.
2019-05-24 17:25:31 +02:00
Romain Vimont
c3779d8513 Make owned serial a pointer-to-non-const
The file handler owns the serial, so it needs to free it. Therefore, it
should not be a pointer-to-const.
2019-05-24 17:24:17 +02:00
Romain Vimont
b3bd5f1b80 Remove useless casts to (void *) 2019-05-24 17:23:21 +02:00
Romain Vimont
a920ba6471 Explain how to customize path in README 2019-05-24 13:25:12 +02:00
Romain Vimont
3133d5d1c7 Continue on icon loading failure
If loading the icon from xpm fails, launch scrcpy without window icon.

<https://github.com/Genymobile/scrcpy/issues/539>
2019-05-23 20:58:08 +02:00
Romain Vimont
2dc1a59471 Check surface returned for icon
SDL_CreateRGBSurfaceFrom() may return NULL, causing a segfault.

<https://github.com/Genymobile/scrcpy/issues/539>
2019-05-20 09:44:45 +02:00
Romain Vimont
3068457b90 Log characters failed to be injected
Some characters may not be injected (e.g. '\r`). Log them instead of
ignoring them silently.
2019-05-20 08:40:10 +02:00
Romain Vimont
56f8e78f58 Merge pull request #542 from npes87184/dev
Return success count in injectText
2019-05-20 08:39:02 +02:00
Yu-Chen Lin
1630f923ef Return success count in injectText
It will insert as many text as possible now.
Fix #509, tested on Windows 10 and Arch Linux.

Signed-off-by: Yu-Chen Lin <npes87184@gmail.com>
2019-05-20 08:36:32 +02:00
Romain Vimont
e443518ed9 Print adb command on error
When the execution of an adb command fails, print the command. This will
help to understand what went wrong.

See <https://github.com/Genymobile/scrcpy/issues/530>.
2019-05-12 15:16:13 +02:00
Romain Vimont
eeb8e8420f Use size_t for command length
The size of an array should have type size_t.
2019-05-12 14:31:18 +02:00
Romain Vimont
39b5893c42 Merge pull request #522 from dos1/compositor
Disable X11 compositor bypass
2019-05-05 17:35:10 +02:00
Sebastian Krzyszkowiak
b941854c73 Disable X11 compositor bypass
Compositor bypass is meant for fullscreen games consuming lots of GPU
resources. For a light app that will usually be windowed, this only
causes unnecessary compositor suspends, especially visible (and
annoying) with complying window manager like KWin.

Signed-off-by: Romain Vimont <rom@rom1v.com>
2019-05-05 17:35:00 +02:00
Sebastian Krzyszkowiak
068253a3a2 Fix mouse focus clickthrough
Mouse focus clickthrough didn't work due to compat.h header not being
included in scrcpy.c.

Signed-off-by: Romain Vimont <rom@rom1v.com>
2019-05-05 17:28:25 +02:00
Romain Vimont
c8338b2918 Recover if expand/collapse panels is not available
Some devices don't have the required method. Recover gracefully without
crashing the server.

Fixes <https://github.com/Genymobile/scrcpy/issues/506>.
2019-05-04 14:49:48 +02:00
Romain Vimont
2837c6eaab Add method to log error without throwable
Add Ln.e(message) in addition to Ln.e(message, error).
2019-05-04 14:49:04 +02:00
Romain Vimont
668e54fd4b Upgrade gradle 2019-05-04 14:49:04 +02:00
Romain Vimont
01664777c8 Merge branch 'master' into dev 2019-05-04 14:48:54 +02:00
Gerdal
ffa8c66979 Fix link error on Windows Subsystem for Linux
Build failed on WSL because of lack of reference to WinMain@16 during
linking.

Fixes <https://github.com/Genymobile/scrcpy/issues/316>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2019-03-31 20:07:07 +02:00
Romain Vimont
5254e585c6 Run server tests on release 2019-03-27 21:51:42 +01:00
Romain Vimont
66baf0f95b Run tests with ASAN enabled
This may capture more errors (like
e2ef39fae5).
2019-03-27 21:50:25 +01:00
Romain Vimont
f11b0ec204 Fix server checkstyle errors
Fix errors reported by:

    gradle -p server check
2019-03-27 21:47:54 +01:00
Romain Vimont
e2ef39fae5 Fix overflow in test
The serialized text is not nul-terminated (its size is explicitely
provided), but the input text in the event is a nul-terminated string.

The test was failing with ASAN enabled.
2019-03-25 11:33:32 +01:00
Romain Vimont
3eda38e5fc Do not call codec.stop() on exception
On exception, the codec is not in a state were .stop() can be called.
2019-03-21 18:46:22 +01:00
27 changed files with 276 additions and 144 deletions

View File

@@ -294,6 +294,19 @@ _¹Double-click on black borders to remove them._
_²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_?
A colleague challenged me to find a name as unpronounceable as [gnirehtet].

View File

@@ -143,18 +143,34 @@ else
link_args = []
endif
executable('scrcpy', src, dependencies: dependencies, include_directories: src_dir, install: true, c_args: c_args, link_args: link_args)
executable('scrcpy', src,
dependencies: dependencies,
include_directories: src_dir,
install: true,
c_args: c_args,
link_args: link_args)
### TESTS
tests = [
['test_control_event_queue', ['tests/test_control_event_queue.c', 'src/control_event.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']],
['test_control_event_queue', [
'tests/test_control_event_queue.c',
'src/control_event.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
exe = executable(t[0], t[1], include_directories: src_dir, dependencies: dependencies)
exe = executable(t[0], t[1],
include_directories: src_dir,
dependencies: dependencies)
test(t[0], exe)
endforeach

View File

@@ -1,5 +1,6 @@
#include "command.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -20,15 +21,52 @@ get_adb_command(void) {
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
show_adb_err_msg(enum process_result err) {
show_adb_err_msg(enum process_result err, const char *const argv[]) {
char buf[512];
switch (err) {
case PROCESS_ERROR_GENERIC:
LOGE("Failed to execute adb");
argv_to_string(argv, buf, sizeof(buf));
LOGE("Failed to execute: %s", buf);
break;
case PROCESS_ERROR_MISSING_BINARY:
LOGE("'adb' command not found (make it accessible from your PATH "
"or define its full path in the ADB environment variable)");
argv_to_string(argv, buf, sizeof(buf));
LOGE("Command not found: %s", buf);
LOGE("(make 'adb' accessible from your PATH or define its full"
"path in the ADB environment variable)");
break;
case PROCESS_SUCCESS:
/* do nothing */
@@ -37,7 +75,7 @@ show_adb_err_msg(enum process_result err) {
}
process_t
adb_execute(const char *serial, const char *const adb_cmd[], int len) {
adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
const char *cmd[len + 4];
int i;
process_t process;
@@ -54,7 +92,7 @@ adb_execute(const char *serial, const char *const adb_cmd[], int len) {
cmd[len + i] = NULL;
enum process_result r = cmd_execute(cmd[0], cmd, &process);
if (r != PROCESS_SUCCESS) {
show_adb_err_msg(r);
show_adb_err_msg(r, cmd);
return PROCESS_NONE;
}
return process;

View File

@@ -49,7 +49,7 @@ 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);
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
process_t
adb_forward(const char *serial, uint16_t local_port,

View File

@@ -43,4 +43,9 @@
# define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
#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

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_BACKSPACE, AKEYCODE_DEL);
MAP(SDLK_TAB, AKEYCODE_TAB);
MAP(SDLK_HOME, AKEYCODE_HOME);
MAP(SDLK_PAGEUP, AKEYCODE_PAGE_UP);
MAP(SDLK_DELETE, AKEYCODE_FORWARD_DEL);
MAP(SDLK_HOME, AKEYCODE_MOVE_HOME);
MAP(SDLK_END, AKEYCODE_MOVE_END);
MAP(SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN);
MAP(SDLK_RIGHT, AKEYCODE_DPAD_RIGHT);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -119,7 +119,7 @@ connect_and_read_byte(uint16_t port) {
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) {
if (net_recv(socket, &byte, 1) != 1) {
// the server is not listening yet behind the adb tunnel
return INVALID_SOCKET;
}
@@ -210,7 +210,7 @@ server_start(struct server *server, const char *serial,
close_socket(&server->server_socket);
}
disable_tunnel(server);
SDL_free((void *) server->serial);
SDL_free(server->serial);
return false;
}
@@ -219,31 +219,44 @@ server_start(struct server *server, const char *serial,
return true;
}
socket_t
bool
server_connect_to(struct server *server) {
if (!server->tunnel_forward) {
server->device_socket = net_accept(server->server_socket);
server->video_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 {
uint32_t attempts = 100;
uint32_t delay = 100; // ms
server->device_socket = connect_to_server(server->local_port, attempts,
delay);
}
server->video_socket =
connect_to_server(server->local_port, attempts, delay);
if (server->video_socket == INVALID_SOCKET) {
return false;
}
if (server->device_socket == INVALID_SOCKET) {
return INVALID_SOCKET;
}
if (!server->tunnel_forward) {
// we don't need the server socket anymore
close_socket(&server->server_socket);
// we know that the device is listening, we don't need several attempts
server->control_socket =
net_connect(IPV4_LOCALHOST, server->local_port);
if (server->control_socket == INVALID_SOCKET) {
return false;
}
}
// we don't need the adb tunnel anymore
disable_tunnel(server); // ignore failure
server->tunnel_enabled = false;
return server->device_socket;
return true;
}
void
@@ -268,8 +281,11 @@ server_destroy(struct server *server) {
if (server->server_socket != INVALID_SOCKET) {
close_socket(&server->server_socket);
}
if (server->device_socket != INVALID_SOCKET) {
close_socket(&server->device_socket);
if (server->video_socket != INVALID_SOCKET) {
close_socket(&server->video_socket);
}
if (server->control_socket != INVALID_SOCKET) {
close_socket(&server->control_socket);
}
SDL_free(server->serial);
}

View File

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

View File

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

View File

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

View File

@@ -49,13 +49,14 @@ static void test_serialize_text_event(void) {
static void test_serialize_long_text_event(void) {
struct control_event event;
event.type = CONTROL_EVENT_TYPE_TEXT;
char text[TEXT_MAX_LENGTH];
char text[TEXT_MAX_LENGTH + 1];
memset(text, 'a', sizeof(text));
text[TEXT_MAX_LENGTH] = '\0';
event.text_event.text = text;
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
int size = control_event_serialize(&event, buf);
assert(size == 3 + sizeof(text));
assert(size == 3 + TEXT_MAX_LENGTH);
unsigned char expected[3 + TEXT_MAX_LENGTH];
expected[0] = 0x01; // CONTROL_EVENT_TYPE_KEYCODE

View File

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

View File

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

View File

@@ -1,13 +1,22 @@
#!/bin/bash
set -e
# build and test locally
# 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
rm -rf "$BUILDDIR"
meson "$BUILDDIR" --buildtype release --strip -Db_lto=true
cd "$BUILDDIR"
ninja
ninja test
cd -
# build Windows releases

View File

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

View File

@@ -1,7 +1,6 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.graphics.Point;
import android.os.SystemClock;
@@ -105,13 +104,16 @@ public class EventController {
return true;
}
private boolean injectText(String text) {
private int injectText(String text) {
int successCount = 0;
for (char c : text.toCharArray()) {
if (!injectChar(c)) {
return false;
Ln.w("Could not inject char u+" + String.format("%04x", (int) c));
continue;
}
successCount++;
}
return true;
return successCount;
}
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.nio.ByteBuffer;
public class IO {
public final class IO {
private IO() {
// not instantiable
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,11 +10,6 @@ public final class SurfaceControl {
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 {
try {
CLASS = Class.forName("android.view.SurfaceControl");
@@ -76,22 +71,6 @@ 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) {
try {
CLASS.getMethod("destroyDisplay", IBinder.class).invoke(null, displayToken);