Compare commits
21 Commits
server_thr
...
server_thr
Author | SHA1 | Date | |
---|---|---|---|
1ad679714b | |||
612d9722a2 | |||
1f8200e295 | |||
4ae7d1e9a9 | |||
49357566cb | |||
c5d962b6fd | |||
54272a931a | |||
59d1b0a269 | |||
58ea238fb2 | |||
13c4aa1a3b | |||
caf594c90e | |||
688477ff65 | |||
ac539e1312 | |||
a57c7d3a2b | |||
d2d18466d4 | |||
dae091e3ab | |||
4c4381de4c | |||
db484d82db | |||
0c9666b733 | |||
8bf28e9f53 | |||
06131ef634 |
@ -26,7 +26,7 @@ struct sc_clock_point {
|
||||
* array.
|
||||
*
|
||||
* To estimate the slope, it splits the last SC_CLOCK_RANGE points into two
|
||||
* sets of SC_CLOCK_RANGE/2 points, and compute their centroid ("average
|
||||
* sets of SC_CLOCK_RANGE/2 points, and computes their centroid ("average
|
||||
* point"). The slope of the estimated affine function is that of the line
|
||||
* passing through these two points.
|
||||
*
|
||||
|
@ -43,6 +43,11 @@
|
||||
# define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
|
||||
#endif
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 6)
|
||||
// <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc>
|
||||
# define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
|
||||
#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
|
||||
|
@ -56,7 +56,7 @@ static const char *const screen_power_mode_labels[] = {
|
||||
};
|
||||
|
||||
static void
|
||||
write_position(uint8_t *buf, const struct position *position) {
|
||||
write_position(uint8_t *buf, const struct sc_position *position) {
|
||||
buffer_write32be(&buf[0], position->point.x);
|
||||
buffer_write32be(&buf[4], position->point.y);
|
||||
buffer_write16be(&buf[8], position->screen_size.width);
|
||||
|
@ -57,11 +57,11 @@ struct control_msg {
|
||||
enum android_motionevent_action action;
|
||||
enum android_motionevent_buttons buttons;
|
||||
uint64_t pointer_id;
|
||||
struct position position;
|
||||
struct sc_position position;
|
||||
float pressure;
|
||||
} inject_touch_event;
|
||||
struct {
|
||||
struct position position;
|
||||
struct sc_position position;
|
||||
int32_t hscroll;
|
||||
int32_t vscroll;
|
||||
} inject_scroll_event;
|
||||
|
@ -3,22 +3,22 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct size {
|
||||
struct sc_size {
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
};
|
||||
|
||||
struct point {
|
||||
struct sc_point {
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
};
|
||||
|
||||
struct position {
|
||||
struct sc_position {
|
||||
// The video screen size may be different from the real device screen size,
|
||||
// so store to which size the absolute position apply, to scale it
|
||||
// accordingly.
|
||||
struct size screen_size;
|
||||
struct point point;
|
||||
struct sc_size screen_size;
|
||||
struct sc_point point;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -1,2 +1,4 @@
|
||||
#define EVENT_NEW_FRAME SDL_USEREVENT
|
||||
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
|
||||
#define EVENT_NEW_FRAME SDL_USEREVENT
|
||||
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
|
||||
#define EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
|
||||
#define EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
|
||||
|
@ -332,7 +332,7 @@ input_manager_process_text_input(struct input_manager *im,
|
||||
static bool
|
||||
simulate_virtual_finger(struct input_manager *im,
|
||||
enum android_motionevent_action action,
|
||||
struct point point) {
|
||||
struct sc_point point) {
|
||||
bool up = action == AMOTION_EVENT_ACTION_UP;
|
||||
|
||||
struct control_msg msg;
|
||||
@ -352,8 +352,8 @@ simulate_virtual_finger(struct input_manager *im,
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct point
|
||||
inverse_point(struct point point, struct size size) {
|
||||
static struct sc_point
|
||||
inverse_point(struct sc_point point, struct sc_size size) {
|
||||
point.x = size.width - point.x;
|
||||
point.y = size.height - point.y;
|
||||
return point;
|
||||
@ -545,10 +545,10 @@ input_manager_process_mouse_motion(struct input_manager *im,
|
||||
im->mp->ops->process_mouse_motion(im->mp, event);
|
||||
|
||||
if (im->vfinger_down) {
|
||||
struct point mouse =
|
||||
struct sc_point mouse =
|
||||
screen_convert_window_to_frame_coords(im->screen, event->x,
|
||||
event->y);
|
||||
struct point vfinger = inverse_point(mouse, im->screen->frame_size);
|
||||
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
|
||||
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
|
||||
}
|
||||
}
|
||||
@ -630,10 +630,10 @@ input_manager_process_mouse_button(struct input_manager *im,
|
||||
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
|
||||
if ((down && !im->vfinger_down && CTRL_PRESSED)
|
||||
|| (!down && im->vfinger_down)) {
|
||||
struct point mouse =
|
||||
struct sc_point mouse =
|
||||
screen_convert_window_to_frame_coords(im->screen, event->x,
|
||||
event->y);
|
||||
struct point vfinger = inverse_point(mouse, im->screen->frame_size);
|
||||
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
|
||||
enum android_motionevent_action action = down
|
||||
? AMOTION_EVENT_ACTION_DOWN
|
||||
: AMOTION_EVENT_ACTION_UP;
|
||||
|
@ -125,7 +125,7 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
|
||||
int mouse_y;
|
||||
SDL_GetMouseState(&mouse_x, &mouse_y);
|
||||
|
||||
struct position position = {
|
||||
struct sc_position position = {
|
||||
.screen_size = screen->frame_size,
|
||||
.point = screen_convert_window_to_frame_coords(screen,
|
||||
mouse_x, mouse_y),
|
||||
|
@ -372,7 +372,7 @@ bool
|
||||
recorder_init(struct recorder *recorder,
|
||||
const char *filename,
|
||||
enum sc_record_format format,
|
||||
struct size declared_frame_size) {
|
||||
struct sc_size declared_frame_size) {
|
||||
recorder->filename = strdup(filename);
|
||||
if (!recorder->filename) {
|
||||
LOGE("Could not strdup filename");
|
||||
|
@ -25,7 +25,7 @@ struct recorder {
|
||||
char *filename;
|
||||
enum sc_record_format format;
|
||||
AVFormatContext *ctx;
|
||||
struct size declared_frame_size;
|
||||
struct sc_size declared_frame_size;
|
||||
bool header_written;
|
||||
|
||||
sc_thread thread;
|
||||
@ -44,7 +44,7 @@ struct recorder {
|
||||
|
||||
bool
|
||||
recorder_init(struct recorder *recorder, const char *filename,
|
||||
enum sc_record_format format, struct size declared_frame_size);
|
||||
enum sc_record_format format, struct sc_size declared_frame_size);
|
||||
|
||||
void
|
||||
recorder_destroy(struct recorder *recorder);
|
||||
|
169
app/src/scrcpy.c
169
app/src/scrcpy.c
@ -57,41 +57,30 @@ struct scrcpy {
|
||||
struct input_manager input_manager;
|
||||
};
|
||||
|
||||
static inline void
|
||||
push_event(uint32_t type, const char *name) {
|
||||
SDL_Event event;
|
||||
event.type = type;
|
||||
int ret = SDL_PushEvent(&event);
|
||||
if (ret < 0) {
|
||||
LOGE("Could not post %s event: %s", name, SDL_GetError());
|
||||
// What could we do?
|
||||
}
|
||||
}
|
||||
#define PUSH_EVENT(TYPE) push_event(TYPE, # TYPE)
|
||||
|
||||
#ifdef _WIN32
|
||||
BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
|
||||
if (ctrl_type == CTRL_C_EVENT) {
|
||||
SDL_Event event;
|
||||
event.type = SDL_QUIT;
|
||||
SDL_PushEvent(&event);
|
||||
PUSH_EVENT(SDL_QUIT);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
// init SDL and set appropriate hints
|
||||
static bool
|
||||
sdl_init_and_configure(bool display, const char *render_driver,
|
||||
bool disable_screensaver) {
|
||||
uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS;
|
||||
if (SDL_Init(flags)) {
|
||||
LOGC("Could not initialize SDL: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
atexit(SDL_Quit);
|
||||
|
||||
#ifdef _WIN32
|
||||
// Clean up properly on Ctrl+C on Windows
|
||||
bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE);
|
||||
if (!ok) {
|
||||
LOGW("Could not set Ctrl+C handler");
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
if (!display) {
|
||||
return true;
|
||||
}
|
||||
static void
|
||||
sdl_set_hints(const char *render_driver) {
|
||||
|
||||
if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) {
|
||||
LOGW("Could not set render driver");
|
||||
@ -109,6 +98,15 @@ sdl_init_and_configure(bool display, const char *render_driver,
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
|
||||
// Disable synthetic mouse events from touch events
|
||||
// Touch events with id SDL_TOUCH_MOUSEID are ignored anyway, but it is
|
||||
// better not to generate them in the first place.
|
||||
if (!SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0")) {
|
||||
LOGW("Could not disable synthetic mouse events");
|
||||
}
|
||||
#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")) {
|
||||
@ -120,6 +118,21 @@ sdl_init_and_configure(bool display, const char *render_driver,
|
||||
if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
|
||||
LOGW("Could not disable minimize on focus loss");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sdl_configure(bool display, bool disable_screensaver) {
|
||||
#ifdef _WIN32
|
||||
// Clean up properly on Ctrl+C on Windows
|
||||
bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE);
|
||||
if (!ok) {
|
||||
LOGW("Could not set Ctrl+C handler");
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
if (!display) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (disable_screensaver) {
|
||||
LOGD("Screensaver disabled");
|
||||
@ -128,8 +141,6 @@ sdl_init_and_configure(bool display, const char *render_driver,
|
||||
LOGD("Screensaver enabled");
|
||||
SDL_EnableScreenSaver();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
@ -206,6 +217,29 @@ event_loop(struct scrcpy *s, const struct scrcpy_options *options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
await_for_server(void) {
|
||||
SDL_Event event;
|
||||
while (SDL_WaitEvent(&event)) {
|
||||
switch (event.type) {
|
||||
case SDL_QUIT:
|
||||
LOGD("User requested to quit");
|
||||
return false;
|
||||
case EVENT_SERVER_CONNECTION_FAILED:
|
||||
LOGE("Server connection failed");
|
||||
return false;
|
||||
case EVENT_SERVER_CONNECTED:
|
||||
LOGD("Server connected");
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LOGE("SDL_WaitEvent() error: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
static SDL_LogPriority
|
||||
sdl_priority_from_av_level(int level) {
|
||||
switch (level) {
|
||||
@ -248,9 +282,32 @@ stream_on_eos(struct stream *stream, void *userdata) {
|
||||
(void) stream;
|
||||
(void) userdata;
|
||||
|
||||
SDL_Event stop_event;
|
||||
stop_event.type = EVENT_STREAM_STOPPED;
|
||||
SDL_PushEvent(&stop_event);
|
||||
PUSH_EVENT(EVENT_STREAM_STOPPED);
|
||||
}
|
||||
|
||||
static void
|
||||
server_on_connection_failed(struct server *server, void *userdata) {
|
||||
(void) server;
|
||||
(void) userdata;
|
||||
|
||||
PUSH_EVENT(EVENT_SERVER_CONNECTION_FAILED);
|
||||
}
|
||||
|
||||
static void
|
||||
server_on_connected(struct server *server, void *userdata) {
|
||||
(void) server;
|
||||
(void) userdata;
|
||||
|
||||
PUSH_EVENT(EVENT_SERVER_CONNECTED);
|
||||
}
|
||||
|
||||
static void
|
||||
server_on_disconnected(struct server *server, void *userdata) {
|
||||
(void) server;
|
||||
(void) userdata;
|
||||
|
||||
LOGD("Server disconnected");
|
||||
// Do nothing, will be managed by the "stream stopped" event
|
||||
}
|
||||
|
||||
bool
|
||||
@ -258,10 +315,14 @@ scrcpy(struct scrcpy_options *options) {
|
||||
static struct scrcpy scrcpy;
|
||||
struct scrcpy *s = &scrcpy;
|
||||
|
||||
if (!server_init(&s->server)) {
|
||||
// Minimal SDL initialization
|
||||
if (SDL_Init(SDL_INIT_EVENTS)) {
|
||||
LOGC("Could not initialize SDL: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
atexit(SDL_Quit);
|
||||
|
||||
bool ret = false;
|
||||
|
||||
bool server_started = false;
|
||||
@ -278,7 +339,6 @@ scrcpy(struct scrcpy_options *options) {
|
||||
bool controller_started = false;
|
||||
bool screen_initialized = false;
|
||||
|
||||
bool record = !!options->record_filename;
|
||||
struct server_params params = {
|
||||
.serial = options->serial,
|
||||
.log_level = options->log_level,
|
||||
@ -297,26 +357,43 @@ scrcpy(struct scrcpy_options *options) {
|
||||
.force_adb_forward = options->force_adb_forward,
|
||||
.power_off_on_close = options->power_off_on_close,
|
||||
};
|
||||
if (!server_start(&s->server, ¶ms)) {
|
||||
|
||||
static const struct server_callbacks cbs = {
|
||||
.on_connection_failed = server_on_connection_failed,
|
||||
.on_connected = server_on_connected,
|
||||
.on_disconnected = server_on_disconnected,
|
||||
};
|
||||
if (!server_init(&s->server, ¶ms, &cbs, NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!server_start(&s->server)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
server_started = true;
|
||||
|
||||
if (!sdl_init_and_configure(options->display, options->render_driver,
|
||||
options->disable_screensaver)) {
|
||||
if (options->display) {
|
||||
sdl_set_hints(options->render_driver);
|
||||
}
|
||||
|
||||
// Initialize SDL video in addition if display is enabled
|
||||
if (options->display && SDL_Init(SDL_INIT_VIDEO)) {
|
||||
LOGC("Could not initialize SDL: %s", SDL_GetError());
|
||||
goto end;
|
||||
}
|
||||
|
||||
char device_name[DEVICE_NAME_FIELD_LENGTH];
|
||||
struct size frame_size;
|
||||
sdl_configure(options->display, options->disable_screensaver);
|
||||
|
||||
if (!server_connect_to(&s->server, device_name, &frame_size)) {
|
||||
// Await for server without blocking Ctrl+C handling
|
||||
if (!await_for_server()) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
struct server_info *info = &s->server.info;
|
||||
|
||||
if (options->display && options->control) {
|
||||
if (!file_handler_init(&s->file_handler, s->server.serial,
|
||||
if (!file_handler_init(&s->file_handler, options->serial,
|
||||
options->push_target)) {
|
||||
goto end;
|
||||
}
|
||||
@ -334,11 +411,11 @@ scrcpy(struct scrcpy_options *options) {
|
||||
}
|
||||
|
||||
struct recorder *rec = NULL;
|
||||
if (record) {
|
||||
if (options->record_filename) {
|
||||
if (!recorder_init(&s->recorder,
|
||||
options->record_filename,
|
||||
options->record_format,
|
||||
frame_size)) {
|
||||
info->frame_size)) {
|
||||
goto end;
|
||||
}
|
||||
rec = &s->recorder;
|
||||
@ -384,11 +461,11 @@ scrcpy(struct scrcpy_options *options) {
|
||||
|
||||
if (options->display) {
|
||||
const char *window_title =
|
||||
options->window_title ? options->window_title : device_name;
|
||||
options->window_title ? options->window_title : info->device_name;
|
||||
|
||||
struct screen_params screen_params = {
|
||||
.window_title = window_title,
|
||||
.frame_size = frame_size,
|
||||
.frame_size = info->frame_size,
|
||||
.always_on_top = options->always_on_top,
|
||||
.window_x = options->window_x,
|
||||
.window_y = options->window_y,
|
||||
@ -411,8 +488,8 @@ scrcpy(struct scrcpy_options *options) {
|
||||
|
||||
#ifdef HAVE_V4L2
|
||||
if (options->v4l2_device) {
|
||||
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, frame_size,
|
||||
options->v4l2_buffer)) {
|
||||
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device,
|
||||
info->frame_size, options->v4l2_buffer)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
|
103
app/src/screen.c
103
app/src/screen.c
@ -14,9 +14,9 @@
|
||||
|
||||
#define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink)
|
||||
|
||||
static inline struct size
|
||||
get_rotated_size(struct size size, int rotation) {
|
||||
struct size rotated_size;
|
||||
static inline struct sc_size
|
||||
get_rotated_size(struct sc_size size, int rotation) {
|
||||
struct sc_size rotated_size;
|
||||
if (rotation & 1) {
|
||||
rotated_size.width = size.height;
|
||||
rotated_size.height = size.width;
|
||||
@ -27,26 +27,26 @@ get_rotated_size(struct size size, int rotation) {
|
||||
return rotated_size;
|
||||
}
|
||||
|
||||
// get the window size in a struct size
|
||||
static struct size
|
||||
// get the window size in a struct sc_size
|
||||
static struct sc_size
|
||||
get_window_size(const struct screen *screen) {
|
||||
int width;
|
||||
int height;
|
||||
SDL_GetWindowSize(screen->window, &width, &height);
|
||||
|
||||
struct size size;
|
||||
struct sc_size size;
|
||||
size.width = width;
|
||||
size.height = height;
|
||||
return size;
|
||||
}
|
||||
|
||||
static struct point
|
||||
static struct sc_point
|
||||
get_window_position(const struct screen *screen) {
|
||||
int x;
|
||||
int y;
|
||||
SDL_GetWindowPosition(screen->window, &x, &y);
|
||||
|
||||
struct point point;
|
||||
struct sc_point point;
|
||||
point.x = x;
|
||||
point.y = y;
|
||||
return point;
|
||||
@ -54,7 +54,7 @@ get_window_position(const struct screen *screen) {
|
||||
|
||||
// set the window size to be applied when fullscreen is disabled
|
||||
static void
|
||||
set_window_size(struct screen *screen, struct size new_size) {
|
||||
set_window_size(struct screen *screen, struct sc_size new_size) {
|
||||
assert(!screen->fullscreen);
|
||||
assert(!screen->maximized);
|
||||
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
|
||||
@ -62,7 +62,7 @@ set_window_size(struct screen *screen, struct size new_size) {
|
||||
|
||||
// get the preferred display bounds (i.e. the screen bounds with some margins)
|
||||
static bool
|
||||
get_preferred_display_bounds(struct size *bounds) {
|
||||
get_preferred_display_bounds(struct sc_size *bounds) {
|
||||
SDL_Rect rect;
|
||||
#ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
|
||||
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r))
|
||||
@ -80,7 +80,7 @@ get_preferred_display_bounds(struct size *bounds) {
|
||||
}
|
||||
|
||||
static bool
|
||||
is_optimal_size(struct size current_size, struct size content_size) {
|
||||
is_optimal_size(struct sc_size current_size, struct sc_size content_size) {
|
||||
// The size is optimal if we can recompute one dimension of the current
|
||||
// size from the other
|
||||
return current_size.height == current_size.width * content_size.height
|
||||
@ -94,16 +94,16 @@ is_optimal_size(struct size current_size, struct size content_size) {
|
||||
// crops the black borders)
|
||||
// - it keeps the aspect ratio
|
||||
// - it scales down to make it fit in the display_size
|
||||
static struct size
|
||||
get_optimal_size(struct size current_size, struct size content_size) {
|
||||
static struct sc_size
|
||||
get_optimal_size(struct sc_size current_size, struct sc_size content_size) {
|
||||
if (content_size.width == 0 || content_size.height == 0) {
|
||||
// avoid division by 0
|
||||
return current_size;
|
||||
}
|
||||
|
||||
struct size window_size;
|
||||
struct sc_size window_size;
|
||||
|
||||
struct size display_size;
|
||||
struct sc_size display_size;
|
||||
if (!get_preferred_display_bounds(&display_size)) {
|
||||
// could not get display bounds, do not constraint the size
|
||||
window_size.width = current_size.width;
|
||||
@ -135,10 +135,10 @@ get_optimal_size(struct size current_size, struct size content_size) {
|
||||
|
||||
// initially, there is no current size, so use the frame size as current size
|
||||
// req_width and req_height, if not 0, are the sizes requested by the user
|
||||
static inline struct size
|
||||
get_initial_optimal_size(struct size content_size, uint16_t req_width,
|
||||
static inline struct sc_size
|
||||
get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
|
||||
uint16_t req_height) {
|
||||
struct size window_size;
|
||||
struct sc_size window_size;
|
||||
if (!req_width && !req_height) {
|
||||
window_size = get_optimal_size(content_size, content_size);
|
||||
} else {
|
||||
@ -166,9 +166,9 @@ screen_update_content_rect(struct screen *screen) {
|
||||
int dh;
|
||||
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
||||
|
||||
struct size content_size = screen->content_size;
|
||||
struct sc_size content_size = screen->content_size;
|
||||
// The drawable size is the window size * the HiDPI scale
|
||||
struct size drawable_size = {dw, dh};
|
||||
struct sc_size drawable_size = {dw, dh};
|
||||
|
||||
SDL_Rect *rect = &screen->rect;
|
||||
|
||||
@ -200,7 +200,7 @@ screen_update_content_rect(struct screen *screen) {
|
||||
static inline SDL_Texture *
|
||||
create_texture(struct screen *screen) {
|
||||
SDL_Renderer *renderer = screen->renderer;
|
||||
struct size size = screen->frame_size;
|
||||
struct sc_size size = screen->frame_size;
|
||||
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
|
||||
SDL_TEXTUREACCESS_STREAMING,
|
||||
size.width, size.height);
|
||||
@ -282,17 +282,33 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
|
||||
(void) vb;
|
||||
struct screen *screen = userdata;
|
||||
|
||||
// event_failed implies previous_skipped (the previous frame may not have
|
||||
// been consumed if the event was not sent)
|
||||
assert(!screen->event_failed || previous_skipped);
|
||||
|
||||
bool need_new_event;
|
||||
if (previous_skipped) {
|
||||
fps_counter_add_skipped_frame(&screen->fps_counter);
|
||||
// The EVENT_NEW_FRAME triggered for the previous frame will consume
|
||||
// this new frame instead
|
||||
// this new frame instead, unless the previous event failed
|
||||
need_new_event = screen->event_failed;
|
||||
} else {
|
||||
need_new_event = true;
|
||||
}
|
||||
|
||||
if (need_new_event) {
|
||||
static SDL_Event new_frame_event = {
|
||||
.type = EVENT_NEW_FRAME,
|
||||
};
|
||||
|
||||
// Post the event on the UI thread
|
||||
SDL_PushEvent(&new_frame_event);
|
||||
int ret = SDL_PushEvent(&new_frame_event);
|
||||
if (ret < 0) {
|
||||
LOGW("Could not post new frame event: %s", SDL_GetError());
|
||||
screen->event_failed = true;
|
||||
} else {
|
||||
screen->event_failed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,6 +318,7 @@ screen_init(struct screen *screen, const struct screen_params *params) {
|
||||
screen->has_frame = false;
|
||||
screen->fullscreen = false;
|
||||
screen->maximized = false;
|
||||
screen->event_failed = false;
|
||||
|
||||
static const struct sc_video_buffer_callbacks cbs = {
|
||||
.on_new_frame = sc_video_buffer_on_new_frame,
|
||||
@ -330,13 +347,13 @@ screen_init(struct screen *screen, const struct screen_params *params) {
|
||||
if (screen->rotation) {
|
||||
LOGI("Initial display rotation set to %u", screen->rotation);
|
||||
}
|
||||
struct size content_size =
|
||||
struct sc_size content_size =
|
||||
get_rotated_size(screen->frame_size, screen->rotation);
|
||||
screen->content_size = content_size;
|
||||
|
||||
struct size window_size = get_initial_optimal_size(content_size,
|
||||
params->window_width,
|
||||
params->window_height);
|
||||
struct sc_size window_size =
|
||||
get_initial_optimal_size(content_size,params->window_width,
|
||||
params->window_height);
|
||||
uint32_t window_flags = SDL_WINDOW_HIDDEN
|
||||
| SDL_WINDOW_RESIZABLE
|
||||
| SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
@ -508,10 +525,10 @@ screen_destroy(struct screen *screen) {
|
||||
}
|
||||
|
||||
static void
|
||||
resize_for_content(struct screen *screen, struct size old_content_size,
|
||||
struct size new_content_size) {
|
||||
struct size window_size = get_window_size(screen);
|
||||
struct size target_size = {
|
||||
resize_for_content(struct screen *screen, struct sc_size old_content_size,
|
||||
struct sc_size new_content_size) {
|
||||
struct sc_size window_size = get_window_size(screen);
|
||||
struct sc_size target_size = {
|
||||
.width = (uint32_t) window_size.width * new_content_size.width
|
||||
/ old_content_size.width,
|
||||
.height = (uint32_t) window_size.height * new_content_size.height
|
||||
@ -522,7 +539,7 @@ resize_for_content(struct screen *screen, struct size old_content_size,
|
||||
}
|
||||
|
||||
static void
|
||||
set_content_size(struct screen *screen, struct size new_content_size) {
|
||||
set_content_size(struct screen *screen, struct sc_size new_content_size) {
|
||||
if (!screen->fullscreen && !screen->maximized) {
|
||||
resize_for_content(screen, screen->content_size, new_content_size);
|
||||
} else if (!screen->resize_pending) {
|
||||
@ -553,7 +570,7 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct size new_content_size =
|
||||
struct sc_size new_content_size =
|
||||
get_rotated_size(screen->frame_size, rotation);
|
||||
|
||||
set_content_size(screen, new_content_size);
|
||||
@ -566,7 +583,7 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
|
||||
|
||||
// recreate the texture and resize the window if the frame size has changed
|
||||
static bool
|
||||
prepare_for_frame(struct screen *screen, struct size new_frame_size) {
|
||||
prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) {
|
||||
if (screen->frame_size.width != new_frame_size.width
|
||||
|| screen->frame_size.height != new_frame_size.height) {
|
||||
// frame dimension changed, destroy texture
|
||||
@ -574,7 +591,7 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) {
|
||||
|
||||
screen->frame_size = new_frame_size;
|
||||
|
||||
struct size new_content_size =
|
||||
struct sc_size new_content_size =
|
||||
get_rotated_size(new_frame_size, screen->rotation);
|
||||
set_content_size(screen, new_content_size);
|
||||
|
||||
@ -615,7 +632,7 @@ screen_update_frame(struct screen *screen) {
|
||||
|
||||
fps_counter_add_rendered_frame(&screen->fps_counter);
|
||||
|
||||
struct size new_frame_size = {frame->width, frame->height};
|
||||
struct sc_size new_frame_size = {frame->width, frame->height};
|
||||
if (!prepare_for_frame(screen, new_frame_size)) {
|
||||
return false;
|
||||
}
|
||||
@ -682,10 +699,10 @@ screen_resize_to_fit(struct screen *screen) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct point point = get_window_position(screen);
|
||||
struct size window_size = get_window_size(screen);
|
||||
struct sc_point point = get_window_position(screen);
|
||||
struct sc_size window_size = get_window_size(screen);
|
||||
|
||||
struct size optimal_size =
|
||||
struct sc_size optimal_size =
|
||||
get_optimal_size(window_size, screen->content_size);
|
||||
|
||||
// Center the window related to the device screen
|
||||
@ -711,7 +728,7 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
|
||||
screen->maximized = false;
|
||||
}
|
||||
|
||||
struct size content_size = screen->content_size;
|
||||
struct sc_size content_size = screen->content_size;
|
||||
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
|
||||
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
|
||||
content_size.height);
|
||||
@ -766,7 +783,7 @@ screen_handle_event(struct screen *screen, SDL_Event *event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct point
|
||||
struct sc_point
|
||||
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
||||
int32_t x, int32_t y) {
|
||||
unsigned rotation = screen->rotation;
|
||||
@ -780,7 +797,7 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
|
||||
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
|
||||
|
||||
// rotate
|
||||
struct point result;
|
||||
struct sc_point result;
|
||||
switch (rotation) {
|
||||
case 0:
|
||||
result.x = x;
|
||||
@ -803,7 +820,7 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
|
||||
return result;
|
||||
}
|
||||
|
||||
struct point
|
||||
struct sc_point
|
||||
screen_convert_window_to_frame_coords(struct screen *screen,
|
||||
int32_t x, int32_t y) {
|
||||
screen_hidpi_scale_coords(screen, &x, &y);
|
||||
|
@ -27,13 +27,13 @@ struct screen {
|
||||
SDL_Renderer *renderer;
|
||||
SDL_Texture *texture;
|
||||
struct sc_opengl gl;
|
||||
struct size frame_size;
|
||||
struct size content_size; // rotated frame_size
|
||||
struct sc_size frame_size;
|
||||
struct sc_size content_size; // rotated frame_size
|
||||
|
||||
bool resize_pending; // resize requested while fullscreen or maximized
|
||||
// The content size the last time the window was not maximized or
|
||||
// fullscreen (meaningful only when resize_pending is true)
|
||||
struct size windowed_content_size;
|
||||
struct sc_size windowed_content_size;
|
||||
|
||||
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
|
||||
unsigned rotation;
|
||||
@ -44,12 +44,14 @@ struct screen {
|
||||
bool maximized;
|
||||
bool mipmaps;
|
||||
|
||||
bool event_failed; // in case SDL_PushEvent() returned an error
|
||||
|
||||
AVFrame *frame;
|
||||
};
|
||||
|
||||
struct screen_params {
|
||||
const char *window_title;
|
||||
struct size frame_size;
|
||||
struct sc_size frame_size;
|
||||
bool always_on_top;
|
||||
|
||||
int16_t window_x;
|
||||
@ -120,13 +122,13 @@ screen_handle_event(struct screen *screen, SDL_Event *event);
|
||||
|
||||
// convert point from window coordinates to frame coordinates
|
||||
// x and y are expressed in pixels
|
||||
struct point
|
||||
struct sc_point
|
||||
screen_convert_window_to_frame_coords(struct screen *screen,
|
||||
int32_t x, int32_t y);
|
||||
|
||||
// convert point from drawable coordinates to frame coordinates
|
||||
// x and y are expressed in pixels
|
||||
struct point
|
||||
struct sc_point
|
||||
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
||||
int32_t x, int32_t y);
|
||||
|
||||
|
288
app/src/server.c
288
app/src/server.c
@ -61,6 +61,44 @@ get_server_path(void) {
|
||||
return server_path;
|
||||
}
|
||||
|
||||
static void
|
||||
server_params_destroy(struct server_params *params) {
|
||||
// The server stores a copy of the params provided by the user
|
||||
free((char *) params->serial);
|
||||
free((char *) params->crop);
|
||||
free((char *) params->codec_options);
|
||||
free((char *) params->encoder_name);
|
||||
}
|
||||
|
||||
static bool
|
||||
server_params_copy(struct server_params *dst, const struct server_params *src) {
|
||||
*dst = *src;
|
||||
|
||||
// The params reference user-allocated memory, so we must copy them to
|
||||
// handle them from another thread
|
||||
|
||||
#define COPY(FIELD) \
|
||||
dst->FIELD = NULL; \
|
||||
if (src->FIELD) { \
|
||||
dst->FIELD = strdup(src->FIELD); \
|
||||
if (!dst->FIELD) { \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
COPY(serial);
|
||||
COPY(crop);
|
||||
COPY(codec_options);
|
||||
COPY(encoder_name);
|
||||
#undef COPY
|
||||
|
||||
return true;
|
||||
|
||||
error:
|
||||
server_params_destroy(dst);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
push_server(const char *serial) {
|
||||
char *server_path = get_server_path();
|
||||
@ -103,24 +141,26 @@ disable_tunnel_forward(const char *serial, uint16_t local_port) {
|
||||
|
||||
static bool
|
||||
disable_tunnel(struct server *server) {
|
||||
const char *serial = server->params.serial;
|
||||
if (server->tunnel_forward) {
|
||||
return disable_tunnel_forward(server->serial, server->local_port);
|
||||
return disable_tunnel_forward(serial, server->local_port);
|
||||
}
|
||||
return disable_tunnel_reverse(server->serial);
|
||||
return disable_tunnel_reverse(serial);
|
||||
}
|
||||
|
||||
static sc_socket
|
||||
listen_on_port(uint16_t port) {
|
||||
static bool
|
||||
listen_on_port(sc_socket socket, uint16_t port) {
|
||||
#define IPV4_LOCALHOST 0x7F000001
|
||||
return net_listen(IPV4_LOCALHOST, port, 1);
|
||||
return net_listen(socket, IPV4_LOCALHOST, port, 1);
|
||||
}
|
||||
|
||||
static bool
|
||||
enable_tunnel_reverse_any_port(struct server *server,
|
||||
struct sc_port_range port_range) {
|
||||
const char *serial = server->params.serial;
|
||||
uint16_t port = port_range.first;
|
||||
for (;;) {
|
||||
if (!enable_tunnel_reverse(server->serial, port)) {
|
||||
if (!enable_tunnel_reverse(serial, port)) {
|
||||
// the command itself failed, it will fail on any port
|
||||
return false;
|
||||
}
|
||||
@ -131,15 +171,21 @@ enable_tunnel_reverse_any_port(struct server *server,
|
||||
// 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(port);
|
||||
if (server->server_socket != SC_INVALID_SOCKET) {
|
||||
// success
|
||||
server->local_port = port;
|
||||
return true;
|
||||
sc_socket server_socket = net_socket();
|
||||
if (server_socket != SC_INVALID_SOCKET) {
|
||||
bool ok = listen_on_port(server_socket, port);
|
||||
if (ok) {
|
||||
// success
|
||||
server->server_socket = server_socket;
|
||||
server->local_port = port;
|
||||
return true;
|
||||
}
|
||||
|
||||
net_close(server_socket);
|
||||
}
|
||||
|
||||
// failure, disable tunnel and try another port
|
||||
if (!disable_tunnel_reverse(server->serial)) {
|
||||
if (!disable_tunnel_reverse(serial)) {
|
||||
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
|
||||
}
|
||||
|
||||
@ -165,9 +211,11 @@ static bool
|
||||
enable_tunnel_forward_any_port(struct server *server,
|
||||
struct sc_port_range port_range) {
|
||||
server->tunnel_forward = true;
|
||||
|
||||
const char *serial = server->params.serial;
|
||||
uint16_t port = port_range.first;
|
||||
for (;;) {
|
||||
if (enable_tunnel_forward(server->serial, port)) {
|
||||
if (enable_tunnel_forward(serial, port)) {
|
||||
// success
|
||||
server->local_port = port;
|
||||
return true;
|
||||
@ -229,6 +277,8 @@ log_level_to_server_string(enum sc_log_level level) {
|
||||
|
||||
static process_t
|
||||
execute_server(struct server *server, const struct server_params *params) {
|
||||
const char *serial = server->params.serial;
|
||||
|
||||
char max_size_string[6];
|
||||
char bit_rate_string[11];
|
||||
char max_fps_string[6];
|
||||
@ -286,16 +336,22 @@ execute_server(struct server *server, const struct server_params *params) {
|
||||
// Port: 5005
|
||||
// Then click on "Debug"
|
||||
#endif
|
||||
return adb_execute(server->serial, cmd, ARRAY_LEN(cmd));
|
||||
return adb_execute(serial, cmd, ARRAY_LEN(cmd));
|
||||
}
|
||||
|
||||
static sc_socket
|
||||
connect_and_read_byte(uint16_t port) {
|
||||
sc_socket socket = net_connect(IPV4_LOCALHOST, port);
|
||||
sc_socket socket = net_socket();
|
||||
if (socket == SC_INVALID_SOCKET) {
|
||||
return SC_INVALID_SOCKET;
|
||||
}
|
||||
|
||||
bool ok = net_connect(socket, IPV4_LOCALHOST, port);
|
||||
if (!ok) {
|
||||
net_close(socket);
|
||||
return SC_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
|
||||
@ -308,7 +364,8 @@ connect_and_read_byte(uint16_t port) {
|
||||
}
|
||||
|
||||
static sc_socket
|
||||
connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
|
||||
connect_to_server(struct server *server, uint32_t attempts, sc_tick delay) {
|
||||
uint16_t port = server->local_port;
|
||||
do {
|
||||
LOGD("Remaining connection attempts: %d", (int) attempts);
|
||||
sc_socket socket = connect_and_read_byte(port);
|
||||
@ -317,29 +374,56 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
|
||||
return socket;
|
||||
}
|
||||
if (attempts) {
|
||||
SDL_Delay(delay);
|
||||
sc_mutex_lock(&server->mutex);
|
||||
// Ignore timedwait return (spurious wake ups are harmless)
|
||||
sc_cond_timedwait(&server->stopped_cond, &server->mutex,
|
||||
sc_tick_now() + delay);
|
||||
bool stopped = server->stopped;
|
||||
sc_mutex_unlock(&server->mutex);
|
||||
if (stopped) {
|
||||
LOGI("Connection attempt stopped");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (--attempts > 0);
|
||||
return SC_INVALID_SOCKET;
|
||||
}
|
||||
|
||||
bool
|
||||
server_init(struct server *server) {
|
||||
server->serial = NULL;
|
||||
server_init(struct server *server, const struct server_params *params,
|
||||
const struct server_callbacks *cbs, void *cbs_userdata) {
|
||||
bool ok = server_params_copy(&server->params, params);
|
||||
if (!ok) {
|
||||
LOGE("Could not copy server params");
|
||||
return false;
|
||||
}
|
||||
|
||||
server->process = PROCESS_NONE;
|
||||
|
||||
bool ok = sc_mutex_init(&server->mutex);
|
||||
ok = sc_mutex_init(&server->mutex);
|
||||
if (!ok) {
|
||||
server_params_destroy(&server->params);
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_cond_init(&server->process_terminated_cond);
|
||||
if (!ok) {
|
||||
sc_mutex_destroy(&server->mutex);
|
||||
server_params_destroy(&server->params);
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_cond_init(&server->stopped_cond);
|
||||
if (!ok) {
|
||||
sc_cond_destroy(&server->process_terminated_cond);
|
||||
sc_mutex_destroy(&server->mutex);
|
||||
server_params_destroy(&server->params);
|
||||
return false;
|
||||
}
|
||||
|
||||
server->process_terminated = false;
|
||||
server->stopped = false;
|
||||
server->connected = false;
|
||||
|
||||
server->server_socket = SC_INVALID_SOCKET;
|
||||
server->video_socket = SC_INVALID_SOCKET;
|
||||
@ -350,13 +434,69 @@ server_init(struct server *server) {
|
||||
server->tunnel_enabled = false;
|
||||
server->tunnel_forward = false;
|
||||
|
||||
assert(cbs);
|
||||
assert(cbs->on_connection_failed);
|
||||
assert(cbs->on_connected);
|
||||
assert(cbs->on_disconnected);
|
||||
|
||||
server->cbs = cbs;
|
||||
server->cbs_userdata = cbs_userdata;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int
|
||||
run_wait_server(void *data) {
|
||||
run_server_connect(void *data) {
|
||||
struct server *server = data;
|
||||
|
||||
if (!server_connect_to(server, &server->info)) {
|
||||
server->cbs->on_connection_failed(server, server->cbs_userdata);
|
||||
goto end;
|
||||
}
|
||||
|
||||
server->connected = true;
|
||||
server->cbs->on_connected(server, server->cbs_userdata);
|
||||
|
||||
end:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
run_server(void *data) {
|
||||
struct server *server = data;
|
||||
|
||||
const struct server_params *params = &server->params;
|
||||
|
||||
if (!push_server(params->serial)) {
|
||||
server->cbs->on_connection_failed(server, server->cbs_userdata);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (!enable_tunnel_any_port(server, params->port_range,
|
||||
params->force_adb_forward)) {
|
||||
server->cbs->on_connection_failed(server, server->cbs_userdata);
|
||||
goto end;
|
||||
}
|
||||
|
||||
server->process = execute_server(server, params);
|
||||
if (server->process == PROCESS_NONE) {
|
||||
server->cbs->on_connection_failed(server, server->cbs_userdata);
|
||||
goto end;
|
||||
}
|
||||
|
||||
sc_thread connect_thread;
|
||||
bool ok = sc_thread_create(&connect_thread, run_server_connect,
|
||||
"server-connect", server);
|
||||
if (!ok) {
|
||||
LOGW("Could not create thread, killing the server...");
|
||||
process_terminate(server->process);
|
||||
server->cbs->on_connection_failed(server, server->cbs_userdata);
|
||||
process_wait(server->process, false); // ignore exit code
|
||||
goto end;
|
||||
}
|
||||
|
||||
process_wait(server->process, false); // ignore exit code
|
||||
LOGD("Server terminated");
|
||||
|
||||
sc_mutex_lock(&server->mutex);
|
||||
server->process_terminated = true;
|
||||
@ -370,64 +510,26 @@ run_wait_server(void *data) {
|
||||
net_interrupt(server->server_socket);
|
||||
}
|
||||
|
||||
LOGD("Server terminated");
|
||||
sc_thread_join(&connect_thread, NULL);
|
||||
|
||||
// Written by connect_thread, sc_thread_join() provides the necessary
|
||||
// memory barrier
|
||||
if (server->connected) {
|
||||
server->cbs->on_disconnected(server, server->cbs_userdata);
|
||||
}
|
||||
// Otherwise, ->on_connection_failed() is already called
|
||||
|
||||
end:
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
server_start(struct server *server, const struct server_params *params) {
|
||||
if (params->serial) {
|
||||
server->serial = strdup(params->serial);
|
||||
if (!server->serial) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!push_server(params->serial)) {
|
||||
/* server->serial will be freed on server_destroy() */
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!enable_tunnel_any_port(server, params->port_range,
|
||||
params->force_adb_forward)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// server will connect to our server socket
|
||||
server->process = execute_server(server, params);
|
||||
if (server->process == PROCESS_NONE) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
// If the server process dies before connecting to the server socket, then
|
||||
// the client will be stuck forever on accept(). To avoid the problem, we
|
||||
// must be able to wake up the accept() call when the server dies. To keep
|
||||
// things simple and multiplatform, just spawn a new thread waiting for the
|
||||
// server process and calling shutdown()/close() on the server socket if
|
||||
// necessary to wake up any accept() blocking call.
|
||||
bool ok = sc_thread_create(&server->wait_server_thread, run_wait_server,
|
||||
"wait-server", server);
|
||||
if (!ok) {
|
||||
process_terminate(server->process);
|
||||
process_wait(server->process, true); // ignore exit code
|
||||
goto error;
|
||||
}
|
||||
|
||||
server->tunnel_enabled = true;
|
||||
|
||||
return true;
|
||||
|
||||
error:
|
||||
// The server socket (if any) will be closed on server_destroy()
|
||||
|
||||
disable_tunnel(server);
|
||||
|
||||
return false;
|
||||
server_start(struct server *server) {
|
||||
return sc_thread_create(&server->thread, run_server, "server", server);
|
||||
}
|
||||
|
||||
static bool
|
||||
device_read_info(sc_socket device_socket, char *device_name,
|
||||
struct size *size) {
|
||||
device_read_info(sc_socket device_socket, struct server_info *info) {
|
||||
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
|
||||
ssize_t r = net_recv_all(device_socket, buf, sizeof(buf));
|
||||
if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
|
||||
@ -436,18 +538,17 @@ device_read_info(sc_socket device_socket, char *device_name,
|
||||
}
|
||||
// in case the client sends garbage
|
||||
buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
|
||||
// strcpy is safe here, since name contains at least
|
||||
// DEVICE_NAME_FIELD_LENGTH bytes and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
|
||||
strcpy(device_name, (char *) buf);
|
||||
size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8)
|
||||
| buf[DEVICE_NAME_FIELD_LENGTH + 1];
|
||||
size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8)
|
||||
| buf[DEVICE_NAME_FIELD_LENGTH + 3];
|
||||
memcpy(info->device_name, (char *) buf, sizeof(info->device_name));
|
||||
|
||||
info->frame_size.width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8)
|
||||
| buf[DEVICE_NAME_FIELD_LENGTH + 1];
|
||||
info->frame_size.height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8)
|
||||
| buf[DEVICE_NAME_FIELD_LENGTH + 3];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
server_connect_to(struct server *server, char *device_name, struct size *size) {
|
||||
server_connect_to(struct server *server, struct server_info *info) {
|
||||
if (!server->tunnel_forward) {
|
||||
server->video_socket = net_accept(server->server_socket);
|
||||
if (server->video_socket == SC_INVALID_SOCKET) {
|
||||
@ -468,19 +569,23 @@ server_connect_to(struct server *server, char *device_name, struct size *size) {
|
||||
server->server_socket = SC_INVALID_SOCKET;
|
||||
} else {
|
||||
uint32_t attempts = 100;
|
||||
uint32_t delay = 100; // ms
|
||||
server->video_socket =
|
||||
connect_to_server(server->local_port, attempts, delay);
|
||||
sc_tick delay = SC_TICK_FROM_MS(100);
|
||||
server->video_socket = connect_to_server(server, attempts, delay);
|
||||
if (server->video_socket == SC_INVALID_SOCKET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// we know that the device is listening, we don't need several attempts
|
||||
server->control_socket =
|
||||
net_connect(IPV4_LOCALHOST, server->local_port);
|
||||
server->control_socket = net_socket();
|
||||
if (server->control_socket == SC_INVALID_SOCKET) {
|
||||
return false;
|
||||
}
|
||||
bool ok = net_connect(server->control_socket, IPV4_LOCALHOST,
|
||||
server->local_port);
|
||||
if (!ok) {
|
||||
net_close(server->control_socket);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// we don't need the adb tunnel anymore
|
||||
@ -488,11 +593,16 @@ server_connect_to(struct server *server, char *device_name, struct size *size) {
|
||||
server->tunnel_enabled = false;
|
||||
|
||||
// The sockets will be closed on stop if device_read_info() fails
|
||||
return device_read_info(server->video_socket, device_name, size);
|
||||
return device_read_info(server->video_socket, info);
|
||||
}
|
||||
|
||||
void
|
||||
server_stop(struct server *server) {
|
||||
sc_mutex_lock(&server->mutex);
|
||||
server->stopped = true;
|
||||
sc_cond_signal(&server->stopped_cond);
|
||||
sc_mutex_unlock(&server->mutex);
|
||||
|
||||
if (server->server_socket != SC_INVALID_SOCKET) {
|
||||
if (!net_interrupt(server->server_socket)) {
|
||||
LOGW("Could not interrupt server socket");
|
||||
@ -537,7 +647,7 @@ server_stop(struct server *server) {
|
||||
process_terminate(server->process);
|
||||
}
|
||||
|
||||
sc_thread_join(&server->wait_server_thread, NULL);
|
||||
sc_thread_join(&server->thread, NULL);
|
||||
process_close(server->process);
|
||||
}
|
||||
|
||||
@ -558,7 +668,9 @@ server_destroy(struct server *server) {
|
||||
LOGW("Could not close control socket");
|
||||
}
|
||||
}
|
||||
free(server->serial);
|
||||
|
||||
server_params_destroy(&server->params);
|
||||
sc_cond_destroy(&server->stopped_cond);
|
||||
sc_cond_destroy(&server->process_terminated_cond);
|
||||
sc_mutex_destroy(&server->mutex);
|
||||
}
|
||||
|
@ -14,21 +14,10 @@
|
||||
#include "util/net.h"
|
||||
#include "util/thread.h"
|
||||
|
||||
struct server {
|
||||
char *serial;
|
||||
process_t process;
|
||||
sc_thread wait_server_thread;
|
||||
|
||||
sc_mutex mutex;
|
||||
sc_cond process_terminated_cond;
|
||||
bool process_terminated;
|
||||
|
||||
sc_socket server_socket; // only used if !tunnel_forward
|
||||
sc_socket video_socket;
|
||||
sc_socket control_socket;
|
||||
uint16_t local_port; // selected from port_range
|
||||
bool tunnel_enabled;
|
||||
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
|
||||
#define DEVICE_NAME_FIELD_LENGTH 64
|
||||
struct server_info {
|
||||
char device_name[DEVICE_NAME_FIELD_LENGTH];
|
||||
struct sc_size frame_size;
|
||||
};
|
||||
|
||||
struct server_params {
|
||||
@ -50,19 +39,54 @@ struct server_params {
|
||||
bool power_off_on_close;
|
||||
};
|
||||
|
||||
// init default values
|
||||
struct server {
|
||||
// The internal allocated strings are copies owned by the server
|
||||
struct server_params params;
|
||||
|
||||
process_t process;
|
||||
sc_thread thread;
|
||||
|
||||
sc_mutex mutex;
|
||||
|
||||
sc_cond process_terminated_cond;
|
||||
bool process_terminated;
|
||||
|
||||
sc_cond stopped_cond;
|
||||
bool stopped;
|
||||
|
||||
bool connected; // written by connect_thread
|
||||
struct server_info info; // initialized once connected
|
||||
|
||||
sc_socket server_socket; // only used if !tunnel_forward
|
||||
sc_socket video_socket;
|
||||
sc_socket control_socket;
|
||||
uint16_t local_port; // selected from port_range
|
||||
bool tunnel_enabled;
|
||||
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
|
||||
|
||||
const struct server_callbacks *cbs;
|
||||
void *cbs_userdata;
|
||||
};
|
||||
|
||||
struct server_callbacks {
|
||||
void (*on_connection_failed)(struct server *server, void *userdata);
|
||||
void (*on_connected)(struct server *server, void *userdata);
|
||||
void (*on_disconnected)(struct server *server, void *userdata);
|
||||
};
|
||||
|
||||
// init the server with the given params
|
||||
bool
|
||||
server_init(struct server *server);
|
||||
server_init(struct server *server, const struct server_params *params,
|
||||
const struct server_callbacks *cbs, void *cbs_userdata);
|
||||
|
||||
// push, enable tunnel et start the server
|
||||
bool
|
||||
server_start(struct server *server, const struct server_params *params);
|
||||
server_start(struct server *server);
|
||||
|
||||
#define DEVICE_NAME_FIELD_LENGTH 64
|
||||
// block until the communication with the server is established
|
||||
// device_name must point to a buffer of at least DEVICE_NAME_FIELD_LENGTH bytes
|
||||
bool
|
||||
server_connect_to(struct server *server, char *device_name, struct size *size);
|
||||
server_connect_to(struct server *server, struct server_info *info);
|
||||
|
||||
// disconnect and kill the server process
|
||||
void
|
||||
|
@ -94,13 +94,18 @@ net_perror(const char *s) {
|
||||
}
|
||||
|
||||
sc_socket
|
||||
net_connect(uint32_t addr, uint16_t port) {
|
||||
net_socket(void) {
|
||||
sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
sc_socket sock = wrap(raw_sock);
|
||||
if (sock == SC_INVALID_SOCKET) {
|
||||
net_perror("socket");
|
||||
return SC_INVALID_SOCKET;
|
||||
}
|
||||
return sock;
|
||||
}
|
||||
|
||||
bool
|
||||
net_connect(sc_socket socket, uint32_t addr, uint16_t port) {
|
||||
sc_raw_socket raw_sock = unwrap(socket);
|
||||
|
||||
SOCKADDR_IN sin;
|
||||
sin.sin_family = AF_INET;
|
||||
@ -109,21 +114,15 @@ net_connect(uint32_t addr, uint16_t port) {
|
||||
|
||||
if (connect(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
|
||||
net_perror("connect");
|
||||
net_close(sock);
|
||||
return SC_INVALID_SOCKET;
|
||||
return false;
|
||||
}
|
||||
|
||||
return sock;
|
||||
return true;
|
||||
}
|
||||
|
||||
sc_socket
|
||||
net_listen(uint32_t addr, uint16_t port, int backlog) {
|
||||
sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
sc_socket sock = wrap(raw_sock);
|
||||
if (sock == SC_INVALID_SOCKET) {
|
||||
net_perror("socket");
|
||||
return SC_INVALID_SOCKET;
|
||||
}
|
||||
bool
|
||||
net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog) {
|
||||
sc_raw_socket raw_sock = unwrap(socket);
|
||||
|
||||
int reuse = 1;
|
||||
if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
|
||||
@ -138,17 +137,15 @@ net_listen(uint32_t addr, uint16_t port, int backlog) {
|
||||
|
||||
if (bind(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
|
||||
net_perror("bind");
|
||||
net_close(sock);
|
||||
return SC_INVALID_SOCKET;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (listen(raw_sock, backlog) == SOCKET_ERROR) {
|
||||
net_perror("listen");
|
||||
net_close(sock);
|
||||
return SC_INVALID_SOCKET;
|
||||
return false;
|
||||
}
|
||||
|
||||
return sock;
|
||||
return true;
|
||||
}
|
||||
|
||||
sc_socket
|
||||
|
@ -31,10 +31,13 @@ void
|
||||
net_cleanup(void);
|
||||
|
||||
sc_socket
|
||||
net_connect(uint32_t addr, uint16_t port);
|
||||
net_socket(void);
|
||||
|
||||
sc_socket
|
||||
net_listen(uint32_t addr, uint16_t port, int backlog);
|
||||
bool
|
||||
net_connect(sc_socket socket, uint32_t addr, uint16_t port);
|
||||
|
||||
bool
|
||||
net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog);
|
||||
|
||||
sc_socket
|
||||
net_accept(sc_socket server_socket);
|
||||
|
@ -359,7 +359,7 @@ sc_v4l2_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||
|
||||
bool
|
||||
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
||||
struct size frame_size, sc_tick buffering_time) {
|
||||
struct sc_size frame_size, sc_tick buffering_time) {
|
||||
vs->device_name = strdup(device_name);
|
||||
if (!vs->device_name) {
|
||||
LOGE("Could not strdup v4l2 device name");
|
||||
|
@ -18,7 +18,7 @@ struct sc_v4l2_sink {
|
||||
AVCodecContext *encoder_ctx;
|
||||
|
||||
char *device_name;
|
||||
struct size frame_size;
|
||||
struct sc_size frame_size;
|
||||
sc_tick buffering_time;
|
||||
|
||||
sc_thread thread;
|
||||
@ -34,7 +34,7 @@ struct sc_v4l2_sink {
|
||||
|
||||
bool
|
||||
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
||||
struct size frame_size, sc_tick buffering_time);
|
||||
struct sc_size frame_size, sc_tick buffering_time);
|
||||
|
||||
void
|
||||
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);
|
||||
|
@ -7,7 +7,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||
classpath 'com.android.tools.build:gradle:7.0.2'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
@ -2,7 +2,7 @@ apply plugin: 'checkstyle'
|
||||
check.dependsOn 'checkstyle'
|
||||
|
||||
checkstyle {
|
||||
toolVersion = '6.19'
|
||||
toolVersion = '9.0.1'
|
||||
}
|
||||
|
||||
task checkstyle(type: Checkstyle) {
|
||||
|
@ -37,6 +37,14 @@ page at http://checkstyle.sourceforge.net/config.html -->
|
||||
|
||||
<module name="SuppressWarningsFilter"/>
|
||||
|
||||
<module name="LineLength">
|
||||
<!-- what is a good max value? -->
|
||||
<property name="max" value="150" />
|
||||
<!-- ignore lines like "$File: //depot/... $" -->
|
||||
<property name="ignorePattern" value="\$File.*\$" />
|
||||
<property name="severity" value="info" />
|
||||
</module>
|
||||
|
||||
<module name="TreeWalker">
|
||||
|
||||
<!-- Checks for Naming Conventions. -->
|
||||
@ -72,13 +80,6 @@ page at http://checkstyle.sourceforge.net/config.html -->
|
||||
|
||||
<!-- Checks for Size Violations. -->
|
||||
<!-- See http://checkstyle.sf.net/config_sizes.html -->
|
||||
<module name="LineLength">
|
||||
<!-- what is a good max value? -->
|
||||
<property name="max" value="150" />
|
||||
<!-- ignore lines like "$File: //depot/... $" -->
|
||||
<property name="ignorePattern" value="\$File.*\$" />
|
||||
<property name="severity" value="info" />
|
||||
</module>
|
||||
<module name="MethodLength" />
|
||||
<module name="ParameterNumber">
|
||||
<property name="ignoreOverriddenMethods" value="true"/>
|
||||
@ -152,26 +153,6 @@ page at http://checkstyle.sourceforge.net/config.html -->
|
||||
</module>
|
||||
<module name="UpperEll" />
|
||||
|
||||
<module name="FileContentsHolder" />
|
||||
<!-- Required by comment suppression filters -->
|
||||
|
||||
</module>
|
||||
|
||||
<module name="SuppressionFilter">
|
||||
<!--<property name="file" value="team-props/checkstyle/checkstyle-suppressions.xml" />-->
|
||||
</module>
|
||||
|
||||
<!-- Enable suppression comments -->
|
||||
<module name="SuppressionCommentFilter">
|
||||
<property name="offCommentFormat" value="CHECKSTYLE IGNORE\s+(\S+)" />
|
||||
<property name="onCommentFormat" value="CHECKSTYLE END IGNORE\s+(\S+)" />
|
||||
<property name="checkFormat" value="$1" />
|
||||
</module>
|
||||
<module name="SuppressWithNearbyCommentFilter">
|
||||
<!-- Syntax is "SUPPRESS CHECKSTYLE name" -->
|
||||
<property name="commentFormat" value="SUPPRESS CHECKSTYLE (\w+)" />
|
||||
<property name="checkFormat" value="$1" />
|
||||
<property name="influenceFormat" value="1" />
|
||||
</module>
|
||||
|
||||
</module>
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -1,11 +1,11 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
compileSdkVersion 31
|
||||
defaultConfig {
|
||||
applicationId "com.genymobile.scrcpy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
targetSdkVersion 31
|
||||
versionCode 11900
|
||||
versionName "1.19"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
|
@ -2,7 +2,6 @@ package com.genymobile.scrcpy;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
|
Reference in New Issue
Block a user