Compare commits
7 Commits
strbuf
...
server_thr
Author | SHA1 | Date | |
---|---|---|---|
b4ee797401 | |||
d7106d7009 | |||
9e6687e698 | |||
dc05033441 | |||
dc652f67ce | |||
55641ceb64 | |||
fb330cc603 |
@ -27,7 +27,6 @@ src = [
|
|||||||
'src/util/log.c',
|
'src/util/log.c',
|
||||||
'src/util/net.c',
|
'src/util/net.c',
|
||||||
'src/util/process.c',
|
'src/util/process.c',
|
||||||
'src/util/strbuf.c',
|
|
||||||
'src/util/str_util.c',
|
'src/util/str_util.c',
|
||||||
'src/util/thread.c',
|
'src/util/thread.c',
|
||||||
'src/util/tick.c',
|
'src/util/tick.c',
|
||||||
@ -187,7 +186,6 @@ if get_option('buildtype') == 'debug'
|
|||||||
'tests/test_cli.c',
|
'tests/test_cli.c',
|
||||||
'src/cli.c',
|
'src/cli.c',
|
||||||
'src/options.c',
|
'src/options.c',
|
||||||
'src/util/strbuf.c',
|
|
||||||
'src/util/str_util.c',
|
'src/util/str_util.c',
|
||||||
]],
|
]],
|
||||||
['test_clock', [
|
['test_clock', [
|
||||||
@ -197,7 +195,6 @@ if get_option('buildtype') == 'debug'
|
|||||||
['test_control_msg_serialize', [
|
['test_control_msg_serialize', [
|
||||||
'tests/test_control_msg_serialize.c',
|
'tests/test_control_msg_serialize.c',
|
||||||
'src/control_msg.c',
|
'src/control_msg.c',
|
||||||
'src/util/strbuf.c',
|
|
||||||
'src/util/str_util.c',
|
'src/util/str_util.c',
|
||||||
]],
|
]],
|
||||||
['test_device_msg_deserialize', [
|
['test_device_msg_deserialize', [
|
||||||
@ -207,13 +204,8 @@ if get_option('buildtype') == 'debug'
|
|||||||
['test_queue', [
|
['test_queue', [
|
||||||
'tests/test_queue.c',
|
'tests/test_queue.c',
|
||||||
]],
|
]],
|
||||||
['test_strbuf', [
|
|
||||||
'tests/test_strbuf.c',
|
|
||||||
'src/util/strbuf.c',
|
|
||||||
]],
|
|
||||||
['test_strutil', [
|
['test_strutil', [
|
||||||
'tests/test_strutil.c',
|
'tests/test_strutil.c',
|
||||||
'src/util/strbuf.c',
|
|
||||||
'src/util/str_util.c',
|
'src/util/str_util.c',
|
||||||
]],
|
]],
|
||||||
]
|
]
|
||||||
|
236
app/src/cli.c
236
app/src/cli.c
@ -8,243 +8,13 @@
|
|||||||
|
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/strbuf.h"
|
|
||||||
#include "util/str_util.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
#define STR_IMPL_(x) #x
|
#define STR_IMPL_(x) #x
|
||||||
#define STR(x) STR_IMPL_(x)
|
#define STR(x) STR_IMPL_(x)
|
||||||
|
|
||||||
struct sc_option {
|
|
||||||
char shortopt;
|
|
||||||
const char *longopt;
|
|
||||||
const char *argdesc;
|
|
||||||
bool optional_arg;
|
|
||||||
const char *text;
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct sc_option options[] = {
|
|
||||||
{
|
|
||||||
.longopt = "always-on-top",
|
|
||||||
.text = "Make scrcpy window always on top (above other windows).",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.shortopt = 'b',
|
|
||||||
.longopt = "bit-rate",
|
|
||||||
.argdesc = "value",
|
|
||||||
.text = "Encode the video at the gitven bit-rate, expressed in bits/s. "
|
|
||||||
"Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
|
|
||||||
"Default is " STR(DEFAULT_BIT_RATE) ".",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.longopt = "codec-options",
|
|
||||||
.argdesc = "key[:type]=value[,...]",
|
|
||||||
.text = "Set a list of comma-separated key:type=value options for the "
|
|
||||||
"device encoder.\n"
|
|
||||||
"The possible values for 'type' are 'int' (default), 'long', "
|
|
||||||
"'float' and 'string'.\n"
|
|
||||||
"The list of possible codec options is available in the "
|
|
||||||
"Android documentation: "
|
|
||||||
"<https://d.android.com/reference/android/media/MediaFormat>",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.longopt = "crop",
|
|
||||||
.argdesc = "width:height:x:y",
|
|
||||||
.text = "Crop the device screen on the server.\n"
|
|
||||||
"The values are expressed in the device natural orientation "
|
|
||||||
"(typically, portrait for a phone, landscape for a tablet). "
|
|
||||||
"Any --max-size value is cmoputed on the cropped size.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.longopt = "disable-screensaver",
|
|
||||||
.text = "Disable screensaver while scrcpy is running.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.longopt = "display",
|
|
||||||
.argdesc = "id",
|
|
||||||
.text = "Specify the display id to mirror.\n"
|
|
||||||
"\n"
|
|
||||||
"The list of possible display ids can be listed by:\n"
|
|
||||||
" adb shell dumpsys display\n"
|
|
||||||
"(search \"mDisplayId=\" in the output)\n"
|
|
||||||
"\n"
|
|
||||||
"Default is 0.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.longopt = "display-buffer",
|
|
||||||
.argdesc = "ms",
|
|
||||||
.text = "Add a buffering delay (in milliseconds) before displaying. "
|
|
||||||
"This increases latency to compensate for jitter.\n"
|
|
||||||
"Default is 0 (no buffering).",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.longopt = "encoder",
|
|
||||||
.argdesc = "name",
|
|
||||||
.text = "Use a specific MediaCodec encoder (must be a H.264 encoder).",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.longopt = "force-adb-forward",
|
|
||||||
.text = "Do not attempt to use \"adb reverse\" to connect to the "
|
|
||||||
"device.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.longopt = "forward-all-clicks",
|
|
||||||
.text = "By default, right-click triggers BACK (or POWER on) and "
|
|
||||||
"middle-click triggers HOME. This option disables these "
|
|
||||||
"shortcuts and forwards the clicks to the device instead.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.shortopt = 'f',
|
|
||||||
.longopt = "fullscreen",
|
|
||||||
.text = "Start in fullscreen.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.shortopt = 'K',
|
|
||||||
.longopt = "hid-keyboard",
|
|
||||||
.text = "Simulate a physical keyboard by using HID over AOAv2.\n"
|
|
||||||
"It provides a better experience for IME users, and allows to "
|
|
||||||
"generate non-ASCII characters, contrary to the default "
|
|
||||||
"injection method.\n"
|
|
||||||
"It may only work over USB, and is currently only supported "
|
|
||||||
"on Linux.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.shortopt = 'h',
|
|
||||||
.longopt = "help",
|
|
||||||
.text = "Print this help.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.longopt = "legacy-paste",
|
|
||||||
.text = "Inject computer clipboard text as a sequence of key events "
|
|
||||||
"on Ctrl+v (like MOD+Shift+v).\n"
|
|
||||||
"This is a workaround for some devices not behaving as "
|
|
||||||
"expected when setting the device clipboard programmatically.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.longopt = "lock-video-orientation",
|
|
||||||
.argdesc = "value",
|
|
||||||
.optional_arg = true,
|
|
||||||
.text = "Lock video orientation to value.\n"
|
|
||||||
"Possible values are \"unlocked\", \"initial\" (locked to the "
|
|
||||||
"initial orientation), 0, 1, 2 and 3. Natural device "
|
|
||||||
"orientation is 0, and each increment adds a 90 degrees "
|
|
||||||
"rotation counterclockwise.\n"
|
|
||||||
"Default is \"unlocked\".\n"
|
|
||||||
"Passing the option without argument is equivalent to passing "
|
|
||||||
"\"initial\".",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.longopt = "max-fps",
|
|
||||||
.argdesc = "value",
|
|
||||||
.text = "Limit the frame rate of screen capture (officially supported "
|
|
||||||
"since Android 10, but may work on earlier versions).",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.shortopt = 'm',
|
|
||||||
.longopt = "max-size",
|
|
||||||
.argdesc = "value",
|
|
||||||
.text = "Limit both the width and height of the video to value. The "
|
|
||||||
"other dimension is computed so that the device aspect-ratio "
|
|
||||||
"is preserved.\n"
|
|
||||||
"Default is 0 (unlimited).",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.shortopt = 'n',
|
|
||||||
.longopt = "no-control",
|
|
||||||
.text = "Disable device control (mirror the device in read-only).",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.shortopt = 'N',
|
|
||||||
.longopt = "no-display",
|
|
||||||
.text = "Do not display device (only when screen recording).",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
print_option_usage_header(const struct sc_option *opt) {
|
|
||||||
struct sc_strbuf buf;
|
|
||||||
if (!sc_strbuf_init(&buf, 128)) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ok = true;
|
|
||||||
(void) ok; // only used for assertions
|
|
||||||
|
|
||||||
if (opt->shortopt) {
|
|
||||||
ok = sc_strbuf_append_char(&buf, '-');
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
|
|
||||||
ok = sc_strbuf_append_char(&buf, opt->shortopt);
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
if (opt->longopt) {
|
|
||||||
ok = sc_strbuf_append_staticstr(&buf, ", ");
|
|
||||||
assert(ok);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opt->longopt) {
|
|
||||||
ok = sc_strbuf_append_staticstr(&buf, "--");
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
if (!sc_strbuf_append_str(&buf, opt->longopt)) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opt->argdesc) {
|
|
||||||
if (opt->optional_arg && !sc_strbuf_append_char(&buf, '[')) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sc_strbuf_append_char(&buf, '=')) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sc_strbuf_append_str(&buf, opt->argdesc)) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opt->optional_arg && !sc_strbuf_append_char(&buf, ']')) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, " %s\n", buf.s);
|
|
||||||
free(buf.s);
|
|
||||||
return;
|
|
||||||
|
|
||||||
error:
|
|
||||||
fprintf(stderr, "<ERROR>\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
print_option_usage(const struct sc_option *opt, unsigned cols) {
|
|
||||||
assert(cols > 20); // We need some space
|
|
||||||
assert(opt->text);
|
|
||||||
|
|
||||||
print_option_usage_header(opt);
|
|
||||||
|
|
||||||
char *text = wrap_lines(opt->text, cols, 8);
|
|
||||||
if (!text) {
|
|
||||||
fprintf(stderr, "<ERROR>\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, "%s\n", text);
|
|
||||||
free(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
scrcpy_print_usage(const char *arg0) {
|
scrcpy_print_usage(const char *arg0) {
|
||||||
fprintf(stderr, "Usage: %s [options]\n"
|
|
||||||
"\n"
|
|
||||||
"Options:\n", arg0);
|
|
||||||
for (size_t i = 0; i < ARRAY_LEN(options); ++i) {
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
print_option_usage(&options[i], 80);
|
|
||||||
}
|
|
||||||
if (false)
|
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Usage: %s [options]\n"
|
"Usage: %s [options]\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -296,12 +66,12 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
"\n"
|
"\n"
|
||||||
" --force-adb-forward\n"
|
" --force-adb-forward\n"
|
||||||
" Do not attempt to use \"adb reverse\" to connect to the\n"
|
" Do not attempt to use \"adb reverse\" to connect to the\n"
|
||||||
" device.\n"
|
" the device.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --forward-all-clicks\n"
|
" --forward-all-clicks\n"
|
||||||
" By default, right-click triggers BACK (or POWER on) and\n"
|
" By default, right-click triggers BACK (or POWER on) and\n"
|
||||||
" middle-click triggers HOME. This option disables these\n"
|
" middle-click triggers HOME. This option disables these\n"
|
||||||
" shortcuts and forwards the clicks to the device instead.\n"
|
" shortcuts and forward the clicks to the device instead.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -f, --fullscreen\n"
|
" -f, --fullscreen\n"
|
||||||
" Start in fullscreen.\n"
|
" Start in fullscreen.\n"
|
||||||
@ -348,7 +118,7 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
"\n"
|
"\n"
|
||||||
" -N, --no-display\n"
|
" -N, --no-display\n"
|
||||||
" Do not display device (only when screen recording is\n"
|
" Do not display device (only when screen recording is\n"
|
||||||
" enabled).\n" // or V4L2 sink
|
" enabled).\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --no-key-repeat\n"
|
" --no-key-repeat\n"
|
||||||
" Do not forward repeated key events when a key is held down.\n"
|
" Do not forward repeated key events when a key is held down.\n"
|
||||||
|
@ -43,11 +43,6 @@
|
|||||||
# define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
|
# define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
|
||||||
#endif
|
#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)
|
#if SDL_VERSION_ATLEAST(2, 0, 8)
|
||||||
// <https://hg.libsdl.org/SDL/rev/dfde5d3f9781>
|
// <https://hg.libsdl.org/SDL/rev/dfde5d3f9781>
|
||||||
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
|
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
|
||||||
|
@ -1,2 +1,5 @@
|
|||||||
#define EVENT_NEW_FRAME SDL_USEREVENT
|
#define EVENT_NEW_FRAME SDL_USEREVENT
|
||||||
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
|
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
|
||||||
|
#define EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
|
||||||
|
#define EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
|
||||||
|
#define EVENT_SERVER_DISCONNECTED (SDL_USEREVENT + 4)
|
||||||
|
161
app/src/scrcpy.c
161
app/src/scrcpy.c
@ -79,8 +79,29 @@ BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
|
|||||||
}
|
}
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|
||||||
static void
|
// init SDL and set appropriate hints
|
||||||
sdl_set_hints(const char *render_driver) {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) {
|
if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) {
|
||||||
LOGW("Could not set render driver");
|
LOGW("Could not set render driver");
|
||||||
@ -98,15 +119,6 @@ sdl_set_hints(const char *render_driver) {
|
|||||||
}
|
}
|
||||||
#endif
|
#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
|
#ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
|
||||||
// Disable compositor bypassing on X11
|
// Disable compositor bypassing on X11
|
||||||
if (!SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0")) {
|
if (!SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0")) {
|
||||||
@ -118,21 +130,6 @@ sdl_set_hints(const char *render_driver) {
|
|||||||
if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
|
if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
|
||||||
LOGW("Could not disable minimize on focus loss");
|
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) {
|
if (disable_screensaver) {
|
||||||
LOGD("Screensaver disabled");
|
LOGD("Screensaver disabled");
|
||||||
@ -141,6 +138,8 @@ sdl_configure(bool display, bool disable_screensaver) {
|
|||||||
LOGD("Screensaver enabled");
|
LOGD("Screensaver enabled");
|
||||||
SDL_EnableScreenSaver();
|
SDL_EnableScreenSaver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@ -159,6 +158,10 @@ static enum event_result
|
|||||||
handle_event(struct scrcpy *s, const struct scrcpy_options *options,
|
handle_event(struct scrcpy *s, const struct scrcpy_options *options,
|
||||||
SDL_Event *event) {
|
SDL_Event *event) {
|
||||||
switch (event->type) {
|
switch (event->type) {
|
||||||
|
case EVENT_SERVER_DISCONNECTED:
|
||||||
|
LOGD("Server disconnected");
|
||||||
|
// Do nothing, will be managed by the "stream stopped" event
|
||||||
|
break;
|
||||||
case EVENT_STREAM_STOPPED:
|
case EVENT_STREAM_STOPPED:
|
||||||
LOGD("Video stream stopped");
|
LOGD("Video stream stopped");
|
||||||
return EVENT_RESULT_STOPPED_BY_EOS;
|
return EVENT_RESULT_STOPPED_BY_EOS;
|
||||||
@ -217,6 +220,32 @@ event_loop(struct scrcpy *s, const struct scrcpy_options *options) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
await_for_server(void) {
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_WaitEvent(&event)) {
|
||||||
|
// Should never receive disconnected event before connected
|
||||||
|
assert(event.type != EVENT_SERVER_DISCONNECTED);
|
||||||
|
|
||||||
|
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
|
static SDL_LogPriority
|
||||||
sdl_priority_from_av_level(int level) {
|
sdl_priority_from_av_level(int level) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
@ -262,23 +291,35 @@ stream_on_eos(struct stream *stream, void *userdata) {
|
|||||||
PUSH_EVENT(EVENT_STREAM_STOPPED);
|
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;
|
||||||
|
|
||||||
|
PUSH_EVENT(EVENT_SERVER_DISCONNECTED);
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
scrcpy(struct scrcpy_options *options) {
|
scrcpy(struct scrcpy_options *options) {
|
||||||
static struct scrcpy scrcpy;
|
static struct scrcpy scrcpy;
|
||||||
struct scrcpy *s = &scrcpy;
|
struct scrcpy *s = &scrcpy;
|
||||||
|
|
||||||
// Minimal SDL initialization
|
|
||||||
if (SDL_Init(SDL_INIT_EVENTS)) {
|
|
||||||
LOGC("Could not initialize SDL: %s", SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
atexit(SDL_Quit);
|
|
||||||
|
|
||||||
if (!server_init(&s->server)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
bool server_started = false;
|
bool server_started = false;
|
||||||
@ -295,6 +336,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
bool controller_started = false;
|
bool controller_started = false;
|
||||||
bool screen_initialized = false;
|
bool screen_initialized = false;
|
||||||
|
|
||||||
|
bool record = !!options->record_filename;
|
||||||
struct server_params params = {
|
struct server_params params = {
|
||||||
.serial = options->serial,
|
.serial = options->serial,
|
||||||
.log_level = options->log_level,
|
.log_level = options->log_level,
|
||||||
@ -313,32 +355,37 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.force_adb_forward = options->force_adb_forward,
|
.force_adb_forward = options->force_adb_forward,
|
||||||
.power_off_on_close = options->power_off_on_close,
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO SDL_Init(SDL_INIT_EVENTS) before starting server
|
||||||
|
if (!server_start(&s->server)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
server_started = true;
|
server_started = true;
|
||||||
|
|
||||||
if (options->display) {
|
if (!sdl_init_and_configure(options->display, options->render_driver,
|
||||||
sdl_set_hints(options->render_driver);
|
options->disable_screensaver)) {
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
sdl_configure(options->display, options->disable_screensaver);
|
// Await for server without blocking Ctrl+C handling
|
||||||
|
if (!await_for_server()) {
|
||||||
struct server_info info;
|
|
||||||
|
|
||||||
if (!server_connect_to(&s->server, &info)) {
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct server_info *info = &s->server.info;
|
||||||
|
|
||||||
if (options->display && options->control) {
|
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)) {
|
options->push_target)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
@ -356,11 +403,11 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct recorder *rec = NULL;
|
struct recorder *rec = NULL;
|
||||||
if (options->record_filename) {
|
if (record) {
|
||||||
if (!recorder_init(&s->recorder,
|
if (!recorder_init(&s->recorder,
|
||||||
options->record_filename,
|
options->record_filename,
|
||||||
options->record_format,
|
options->record_format,
|
||||||
info.frame_size)) {
|
info->frame_size)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
rec = &s->recorder;
|
rec = &s->recorder;
|
||||||
@ -406,11 +453,11 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
|
|
||||||
if (options->display) {
|
if (options->display) {
|
||||||
const char *window_title =
|
const char *window_title =
|
||||||
options->window_title ? options->window_title : info.device_name;
|
options->window_title ? options->window_title : info->device_name;
|
||||||
|
|
||||||
struct screen_params screen_params = {
|
struct screen_params screen_params = {
|
||||||
.window_title = window_title,
|
.window_title = window_title,
|
||||||
.frame_size = info.frame_size,
|
.frame_size = info->frame_size,
|
||||||
.always_on_top = options->always_on_top,
|
.always_on_top = options->always_on_top,
|
||||||
.window_x = options->window_x,
|
.window_x = options->window_x,
|
||||||
.window_y = options->window_y,
|
.window_y = options->window_y,
|
||||||
@ -434,7 +481,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
if (options->v4l2_device) {
|
if (options->v4l2_device) {
|
||||||
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device,
|
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device,
|
||||||
info.frame_size, options->v4l2_buffer)) {
|
info->frame_size, options->v4l2_buffer)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
196
app/src/server.c
196
app/src/server.c
@ -61,6 +61,44 @@ get_server_path(void) {
|
|||||||
return server_path;
|
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
|
static bool
|
||||||
push_server(const char *serial) {
|
push_server(const char *serial) {
|
||||||
char *server_path = get_server_path();
|
char *server_path = get_server_path();
|
||||||
@ -103,10 +141,11 @@ disable_tunnel_forward(const char *serial, uint16_t local_port) {
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
disable_tunnel(struct server *server) {
|
disable_tunnel(struct server *server) {
|
||||||
|
const char *serial = server->params.serial;
|
||||||
if (server->tunnel_forward) {
|
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
|
static sc_socket
|
||||||
@ -118,9 +157,10 @@ listen_on_port(uint16_t port) {
|
|||||||
static bool
|
static bool
|
||||||
enable_tunnel_reverse_any_port(struct server *server,
|
enable_tunnel_reverse_any_port(struct server *server,
|
||||||
struct sc_port_range port_range) {
|
struct sc_port_range port_range) {
|
||||||
|
const char *serial = server->params.serial;
|
||||||
uint16_t port = port_range.first;
|
uint16_t port = port_range.first;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (!enable_tunnel_reverse(server->serial, port)) {
|
if (!enable_tunnel_reverse(serial, port)) {
|
||||||
// the command itself failed, it will fail on any port
|
// the command itself failed, it will fail on any port
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -139,7 +179,7 @@ enable_tunnel_reverse_any_port(struct server *server,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// failure, disable tunnel and try another port
|
// 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);
|
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,9 +205,11 @@ static bool
|
|||||||
enable_tunnel_forward_any_port(struct server *server,
|
enable_tunnel_forward_any_port(struct server *server,
|
||||||
struct sc_port_range port_range) {
|
struct sc_port_range port_range) {
|
||||||
server->tunnel_forward = true;
|
server->tunnel_forward = true;
|
||||||
|
|
||||||
|
const char *serial = server->params.serial;
|
||||||
uint16_t port = port_range.first;
|
uint16_t port = port_range.first;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (enable_tunnel_forward(server->serial, port)) {
|
if (enable_tunnel_forward(serial, port)) {
|
||||||
// success
|
// success
|
||||||
server->local_port = port;
|
server->local_port = port;
|
||||||
return true;
|
return true;
|
||||||
@ -229,6 +271,8 @@ log_level_to_server_string(enum sc_log_level level) {
|
|||||||
|
|
||||||
static process_t
|
static process_t
|
||||||
execute_server(struct server *server, const struct server_params *params) {
|
execute_server(struct server *server, const struct server_params *params) {
|
||||||
|
const char *serial = server->params.serial;
|
||||||
|
|
||||||
char max_size_string[6];
|
char max_size_string[6];
|
||||||
char bit_rate_string[11];
|
char bit_rate_string[11];
|
||||||
char max_fps_string[6];
|
char max_fps_string[6];
|
||||||
@ -286,7 +330,7 @@ execute_server(struct server *server, const struct server_params *params) {
|
|||||||
// Port: 5005
|
// Port: 5005
|
||||||
// Then click on "Debug"
|
// Then click on "Debug"
|
||||||
#endif
|
#endif
|
||||||
return adb_execute(server->serial, cmd, ARRAY_LEN(cmd));
|
return adb_execute(serial, cmd, ARRAY_LEN(cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
static sc_socket
|
static sc_socket
|
||||||
@ -316,6 +360,7 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
|
|||||||
// it worked!
|
// it worked!
|
||||||
return socket;
|
return socket;
|
||||||
}
|
}
|
||||||
|
// TODO use mutex + condvar + bool stopped
|
||||||
if (attempts) {
|
if (attempts) {
|
||||||
SDL_Delay(delay);
|
SDL_Delay(delay);
|
||||||
}
|
}
|
||||||
@ -324,22 +369,31 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
server_init(struct server *server) {
|
server_init(struct server *server, const struct server_params *params,
|
||||||
server->serial = NULL;
|
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;
|
server->process = PROCESS_NONE;
|
||||||
|
|
||||||
bool ok = sc_mutex_init(&server->mutex);
|
ok = sc_mutex_init(&server->mutex);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
|
server_params_destroy(&server->params);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_cond_init(&server->process_terminated_cond);
|
ok = sc_cond_init(&server->process_terminated_cond);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
sc_mutex_destroy(&server->mutex);
|
sc_mutex_destroy(&server->mutex);
|
||||||
|
server_params_destroy(&server->params);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
server->process_terminated = false;
|
server->process_terminated = false;
|
||||||
|
server->connected = false;
|
||||||
|
|
||||||
server->server_socket = SC_INVALID_SOCKET;
|
server->server_socket = SC_INVALID_SOCKET;
|
||||||
server->video_socket = SC_INVALID_SOCKET;
|
server->video_socket = SC_INVALID_SOCKET;
|
||||||
@ -350,13 +404,69 @@ server_init(struct server *server) {
|
|||||||
server->tunnel_enabled = false;
|
server->tunnel_enabled = false;
|
||||||
server->tunnel_forward = 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
run_wait_server(void *data) {
|
run_server_connect(void *data) {
|
||||||
struct server *server = 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
|
process_wait(server->process, false); // ignore exit code
|
||||||
|
LOGD("Server terminated");
|
||||||
|
|
||||||
sc_mutex_lock(&server->mutex);
|
sc_mutex_lock(&server->mutex);
|
||||||
server->process_terminated = true;
|
server->process_terminated = true;
|
||||||
@ -370,59 +480,22 @@ run_wait_server(void *data) {
|
|||||||
net_interrupt(server->server_socket);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
server_start(struct server *server, const struct server_params *params) {
|
server_start(struct server *server) {
|
||||||
if (params->serial) {
|
return sc_thread_create(&server->thread, run_server, "server", server);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@ -535,7 +608,7 @@ server_stop(struct server *server) {
|
|||||||
process_terminate(server->process);
|
process_terminate(server->process);
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_thread_join(&server->wait_server_thread, NULL);
|
sc_thread_join(&server->thread, NULL);
|
||||||
process_close(server->process);
|
process_close(server->process);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -556,7 +629,8 @@ server_destroy(struct server *server) {
|
|||||||
LOGW("Could not close control socket");
|
LOGW("Could not close control socket");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
free(server->serial);
|
|
||||||
|
server_params_destroy(&server->params);
|
||||||
sc_cond_destroy(&server->process_terminated_cond);
|
sc_cond_destroy(&server->process_terminated_cond);
|
||||||
sc_mutex_destroy(&server->mutex);
|
sc_mutex_destroy(&server->mutex);
|
||||||
}
|
}
|
||||||
|
@ -20,23 +20,6 @@ struct server_info {
|
|||||||
struct sc_size frame_size;
|
struct sc_size frame_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
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"
|
|
||||||
};
|
|
||||||
|
|
||||||
struct server_params {
|
struct server_params {
|
||||||
const char *serial;
|
const char *serial;
|
||||||
enum sc_log_level log_level;
|
enum sc_log_level log_level;
|
||||||
@ -56,15 +39,48 @@ struct server_params {
|
|||||||
bool power_off_on_close;
|
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;
|
||||||
|
|
||||||
|
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
|
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
|
// push, enable tunnel et start the server
|
||||||
bool
|
bool
|
||||||
server_start(struct server *server, const struct server_params *params);
|
server_start(struct server *server);
|
||||||
|
|
||||||
// block until the communication with the server is established
|
// 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
|
bool
|
||||||
server_connect_to(struct server *server, struct server_info *info);
|
server_connect_to(struct server *server, struct server_info *info);
|
||||||
|
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
#include "str_util.h"
|
#include "str_util.h"
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "util/strbuf.h"
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
# include <windows.h>
|
# include <windows.h>
|
||||||
@ -211,76 +209,3 @@ utf8_from_wide_char(const wchar_t *ws) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
char *wrap_lines(const char *input, unsigned columns, unsigned indent) {
|
|
||||||
struct sc_strbuf buf;
|
|
||||||
|
|
||||||
// The output string should not be a lot longer than the input string (just
|
|
||||||
// a few '\n' added), so this initial capacity should almost always avoid
|
|
||||||
// internal realloc() in string buffer
|
|
||||||
size_t cap = strlen(input) * 3 / 2;
|
|
||||||
|
|
||||||
if (!sc_strbuf_init(&buf, cap)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(indent < columns);
|
|
||||||
|
|
||||||
#define APPEND(S,N) if (!sc_strbuf_append(&buf, S, N)) goto error
|
|
||||||
#define APPEND_CHAR(C) if (!sc_strbuf_append_char(&buf, C)) goto error
|
|
||||||
#define APPEND_N(C,N) if (!sc_strbuf_append_n(&buf, C, N)) goto error
|
|
||||||
#define APPEND_INDENT() if (indent) APPEND_N(' ', indent)
|
|
||||||
|
|
||||||
APPEND_INDENT();
|
|
||||||
|
|
||||||
// The last separator encountered, it must be inserted only conditionnaly,
|
|
||||||
// depending on the next token
|
|
||||||
char pending = 0;
|
|
||||||
|
|
||||||
// col tracks the current column in the current line
|
|
||||||
size_t col = indent;
|
|
||||||
while (*input) {
|
|
||||||
size_t sep_idx = strcspn(input, "\n ");
|
|
||||||
bool wrap = col + sep_idx > columns;
|
|
||||||
|
|
||||||
char sep = input[sep_idx];
|
|
||||||
if (sep == ' ')
|
|
||||||
sep = ' ';
|
|
||||||
|
|
||||||
if (wrap) {
|
|
||||||
APPEND_CHAR('\n');
|
|
||||||
APPEND_INDENT();
|
|
||||||
col = indent;
|
|
||||||
} else if (pending) {
|
|
||||||
APPEND_CHAR(pending);
|
|
||||||
++col;
|
|
||||||
if (pending == '\n')
|
|
||||||
{
|
|
||||||
APPEND_INDENT();
|
|
||||||
col = indent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sep_idx) {
|
|
||||||
APPEND(input, sep_idx);
|
|
||||||
col += sep_idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
pending = sep;
|
|
||||||
|
|
||||||
input += sep_idx;
|
|
||||||
if (*input != '\0') {
|
|
||||||
// Skip the separator
|
|
||||||
++input;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pending)
|
|
||||||
APPEND_CHAR(pending);
|
|
||||||
|
|
||||||
return buf.s;
|
|
||||||
|
|
||||||
error:
|
|
||||||
free(buf.s);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
@ -62,8 +62,4 @@ char *
|
|||||||
utf8_from_wide_char(const wchar_t *s);
|
utf8_from_wide_char(const wchar_t *s);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Wrap input lines at words boundaries (spaces) so that they fit in 'columns'
|
|
||||||
// columns, left-indented by 'indent' spaces
|
|
||||||
char *wrap_lines(const char *input, unsigned columns, unsigned indent);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
#include "strbuf.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap) {
|
|
||||||
buf->s = malloc(init_cap + 1); // +1 for '\0'
|
|
||||||
if (!buf->s) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf->len = 0;
|
|
||||||
buf->cap = init_cap;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
sc_strbuf_reserve(struct sc_strbuf *buf, size_t len) {
|
|
||||||
if (buf->len + len > buf->cap) {
|
|
||||||
fprintf(stderr, "realloc\n");
|
|
||||||
size_t new_cap = buf->cap * 3 / 2 + len;
|
|
||||||
char *s = realloc(buf->s, new_cap + 1); // +1 for '\0'
|
|
||||||
if (!s) {
|
|
||||||
// Leave the old buf->s
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
buf->s = s;
|
|
||||||
buf->cap = new_cap;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len) {
|
|
||||||
assert(s);
|
|
||||||
assert(*s);
|
|
||||||
assert(strlen(s) >= len);
|
|
||||||
if (!sc_strbuf_reserve(buf, len)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(&buf->s[buf->len], s, len);
|
|
||||||
buf->len += len;
|
|
||||||
buf->s[buf->len] = '\0';
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_strbuf_append_char(struct sc_strbuf *buf, const char c) {
|
|
||||||
if (!sc_strbuf_reserve(buf, 1)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf->s[buf->len] = c;
|
|
||||||
buf->len ++;
|
|
||||||
buf->s[buf->len] = '\0';
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n) {
|
|
||||||
if (!sc_strbuf_reserve(buf, n)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(&buf->s[buf->len], c, n);
|
|
||||||
buf->len += n;
|
|
||||||
buf->s[buf->len] = '\0';
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
#ifndef SC_STRBUF_H
|
|
||||||
#define SC_STRBUF_H
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
struct sc_strbuf {
|
|
||||||
char *s;
|
|
||||||
size_t len;
|
|
||||||
size_t cap;
|
|
||||||
};
|
|
||||||
|
|
||||||
// buf->s must be manually freed by the caller
|
|
||||||
bool
|
|
||||||
sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap);
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len);
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_strbuf_append_char(struct sc_strbuf *buf, const char c);
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n);
|
|
||||||
|
|
||||||
static inline bool
|
|
||||||
sc_strbuf_append_str(struct sc_strbuf *buf, const char *s) {
|
|
||||||
return sc_strbuf_append(buf, s, strlen(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append static string (i.e. the string size is known at compile time, for
|
|
||||||
// example a string literal)
|
|
||||||
#define sc_strbuf_append_staticstr(BUF, S) \
|
|
||||||
sc_strbuf_append(BUF, S, sizeof(S) - 1)
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,8 +1,6 @@
|
|||||||
#ifndef SC_TICK_H
|
#ifndef SC_TICK_H
|
||||||
#define SC_TICK_H
|
#define SC_TICK_H
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
typedef int64_t sc_tick;
|
typedef int64_t sc_tick;
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "util/strbuf.h"
|
|
||||||
|
|
||||||
static void test_strbuf_simple(void) {
|
|
||||||
struct sc_strbuf buf;
|
|
||||||
bool ok = sc_strbuf_init(&buf, 10);
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
ok = sc_strbuf_append_staticstr(&buf, "Hello");
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
ok = sc_strbuf_append_char(&buf, ' ');
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
ok = sc_strbuf_append_staticstr(&buf, "world");
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
ok = sc_strbuf_append_staticstr(&buf, "!\n");
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
ok = sc_strbuf_append_staticstr(&buf, "This is a test");
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
ok = sc_strbuf_append_n(&buf, '.', 3);
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
assert(!strcmp(buf.s, "Hello world!\nThis is a test..."));
|
|
||||||
free(buf.s);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
(void) argc;
|
|
||||||
(void) argv;
|
|
||||||
|
|
||||||
test_strbuf_simple();
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -299,45 +299,6 @@ static void test_strlist_contains(void) {
|
|||||||
assert(strlist_contains("xyz", '\0', "xyz"));
|
assert(strlist_contains("xyz", '\0', "xyz"));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_wrap_lines(void) {
|
|
||||||
const char *s = "This is a text to test line wrapping. The lines must be "
|
|
||||||
"wrapped either at a space or a line break.\n"
|
|
||||||
"\n"
|
|
||||||
"This rectangle must remains a rectangle because it is "
|
|
||||||
"drawn in lines having lengths lower than the specified "
|
|
||||||
"number of columns:\n"
|
|
||||||
" +----+\n"
|
|
||||||
" | |\n"
|
|
||||||
" +----+\n";
|
|
||||||
|
|
||||||
// |---- 1 1 2 2|
|
|
||||||
// |0 5 0 5 0 3| <-- 24 columns
|
|
||||||
const char *expected = " This is a text to\n"
|
|
||||||
" test line wrapping.\n"
|
|
||||||
" The lines must be\n"
|
|
||||||
" wrapped either at a\n"
|
|
||||||
" space or a line\n"
|
|
||||||
" break.\n"
|
|
||||||
" \n"
|
|
||||||
" This rectangle must\n"
|
|
||||||
" remains a rectangle\n"
|
|
||||||
" because it is drawn\n"
|
|
||||||
" in lines having\n"
|
|
||||||
" lengths lower than\n"
|
|
||||||
" the specified number\n"
|
|
||||||
" of columns:\n"
|
|
||||||
" +----+\n"
|
|
||||||
" | |\n"
|
|
||||||
" +----+\n";
|
|
||||||
|
|
||||||
char *formatted = wrap_lines(s, 24, 4);
|
|
||||||
assert(formatted);
|
|
||||||
|
|
||||||
assert(!strcmp(formatted, expected));
|
|
||||||
|
|
||||||
free(formatted);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
(void) argc;
|
(void) argc;
|
||||||
(void) argv;
|
(void) argv;
|
||||||
@ -356,6 +317,5 @@ int main(int argc, char *argv[]) {
|
|||||||
test_parse_integers();
|
test_parse_integers();
|
||||||
test_parse_integer_with_suffix();
|
test_parse_integer_with_suffix();
|
||||||
test_strlist_contains();
|
test_strlist_contains();
|
||||||
test_wrap_lines();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user