2018-01-23 23:32:29 +08:00
|
|
|
#include "scrcpy.h"
|
2017-12-12 22:12:07 +08:00
|
|
|
|
2018-01-23 23:32:29 +08:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
2017-12-12 22:12:07 +08:00
|
|
|
#include <unistd.h>
|
|
|
|
#include <libavformat/avformat.h>
|
2018-01-23 23:32:29 +08:00
|
|
|
#include <sys/time.h>
|
2017-12-12 22:12:07 +08:00
|
|
|
#include <SDL2/SDL.h>
|
|
|
|
|
2020-05-08 20:54:33 +08:00
|
|
|
#ifdef _WIN32
|
2020-06-20 04:04:06 +08:00
|
|
|
// not needed here, but winsock2.h must never be included AFTER windows.h
|
|
|
|
# include <winsock2.h>
|
2020-05-08 20:54:33 +08:00
|
|
|
# include <windows.h>
|
|
|
|
#endif
|
|
|
|
|
2018-02-08 18:26:31 +08:00
|
|
|
#include "controller.h"
|
2018-01-23 23:32:29 +08:00
|
|
|
#include "decoder.h"
|
|
|
|
#include "events.h"
|
2022-01-22 02:10:27 +08:00
|
|
|
#include "file_pusher.h"
|
2021-10-03 23:11:20 +08:00
|
|
|
#include "keyboard_inject.h"
|
2021-10-03 23:44:14 +08:00
|
|
|
#include "mouse_inject.h"
|
2018-11-09 19:21:17 +08:00
|
|
|
#include "recorder.h"
|
2018-02-08 18:14:13 +08:00
|
|
|
#include "screen.h"
|
2018-01-23 23:32:29 +08:00
|
|
|
#include "server.h"
|
2019-03-02 23:43:43 +08:00
|
|
|
#include "stream.h"
|
2022-01-25 05:27:15 +08:00
|
|
|
#ifdef HAVE_AOA_HID
|
|
|
|
# include "usb/aoa_hid.h"
|
|
|
|
# include "usb/hid_keyboard.h"
|
|
|
|
# include "usb/hid_mouse.h"
|
|
|
|
#endif
|
Wait SET_CLIPBOARD ack before Ctrl+v via HID
To allow seamless copy-paste, on Ctrl+v, a SET_CLIPBOARD request is
performed before injecting Ctrl+v.
But when HID keyboard is enabled, the Ctrl+v injection is not sent on
the same channel as the clipboard request, so they are not serialized,
and may occur in any order. If Ctrl+v happens to be injected before the
new clipboard content is set, then the old content is pasted instead,
which is incorrect.
To minimize the probability of occurrence of the wrong order, a delay of
2 milliseconds was added before injecting Ctrl+v. Then 5ms. But even
with 5ms, the wrong behavior sometimes happens.
To handle it properly, add an acknowledgement mechanism, so that Ctrl+v
is injected over AOA only after the SET_CLIPBOARD request has been
performed and acknowledged by the server.
Refs e4163321f00bb3830c6049bdb6c1515e7cc668a0
Refs 45b0f8123a52f5c73a5860d616f4ceba2766ca6a
PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-22 00:40:11 +08:00
|
|
|
#include "util/acksync.h"
|
2019-11-24 18:53:00 +08:00
|
|
|
#include "util/log.h"
|
|
|
|
#include "util/net.h"
|
2021-04-04 06:10:44 +08:00
|
|
|
#ifdef HAVE_V4L2
|
|
|
|
# include "v4l2_sink.h"
|
|
|
|
#endif
|
2018-01-23 23:32:29 +08:00
|
|
|
|
2021-05-29 03:29:14 +08:00
|
|
|
struct scrcpy {
|
2021-11-13 06:24:12 +08:00
|
|
|
struct sc_server server;
|
2022-01-15 05:17:30 +08:00
|
|
|
struct sc_screen screen;
|
2021-05-29 03:29:14 +08:00
|
|
|
struct stream stream;
|
|
|
|
struct decoder decoder;
|
|
|
|
struct recorder recorder;
|
2021-04-04 06:10:44 +08:00
|
|
|
#ifdef HAVE_V4L2
|
2021-05-29 03:29:14 +08:00
|
|
|
struct sc_v4l2_sink v4l2_sink;
|
2021-04-04 06:10:44 +08:00
|
|
|
#endif
|
2022-01-15 05:17:30 +08:00
|
|
|
struct sc_controller controller;
|
2022-01-22 02:10:27 +08:00
|
|
|
struct sc_file_pusher file_pusher;
|
2021-09-10 18:57:35 +08:00
|
|
|
#ifdef HAVE_AOA_HID
|
|
|
|
struct sc_aoa aoa;
|
Wait SET_CLIPBOARD ack before Ctrl+v via HID
To allow seamless copy-paste, on Ctrl+v, a SET_CLIPBOARD request is
performed before injecting Ctrl+v.
But when HID keyboard is enabled, the Ctrl+v injection is not sent on
the same channel as the clipboard request, so they are not serialized,
and may occur in any order. If Ctrl+v happens to be injected before the
new clipboard content is set, then the old content is pasted instead,
which is incorrect.
To minimize the probability of occurrence of the wrong order, a delay of
2 milliseconds was added before injecting Ctrl+v. Then 5ms. But even
with 5ms, the wrong behavior sometimes happens.
To handle it properly, add an acknowledgement mechanism, so that Ctrl+v
is injected over AOA only after the SET_CLIPBOARD request has been
performed and acknowledged by the server.
Refs e4163321f00bb3830c6049bdb6c1515e7cc668a0
Refs 45b0f8123a52f5c73a5860d616f4ceba2766ca6a
PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-22 00:40:11 +08:00
|
|
|
// sequence/ack helper to synchronize clipboard and Ctrl+v via HID
|
|
|
|
struct sc_acksync acksync;
|
2021-09-10 18:57:35 +08:00
|
|
|
#endif
|
|
|
|
union {
|
|
|
|
struct sc_keyboard_inject keyboard_inject;
|
|
|
|
#ifdef HAVE_AOA_HID
|
|
|
|
struct sc_hid_keyboard keyboard_hid;
|
|
|
|
#endif
|
|
|
|
};
|
2021-12-27 05:32:51 +08:00
|
|
|
union {
|
|
|
|
struct sc_mouse_inject mouse_inject;
|
|
|
|
#ifdef HAVE_AOA_HID
|
|
|
|
struct sc_hid_mouse mouse_hid;
|
|
|
|
#endif
|
|
|
|
};
|
2021-05-29 03:29:14 +08:00
|
|
|
};
|
2018-02-15 19:07:47 +08:00
|
|
|
|
2021-10-31 04:36:56 +08:00
|
|
|
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)
|
|
|
|
|
2020-05-08 20:54:33 +08:00
|
|
|
#ifdef _WIN32
|
2020-05-11 07:32:54 +08:00
|
|
|
BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
|
2020-05-08 20:54:33 +08:00
|
|
|
if (ctrl_type == CTRL_C_EVENT) {
|
2021-10-31 04:36:56 +08:00
|
|
|
PUSH_EVENT(SDL_QUIT);
|
2020-05-08 20:54:33 +08:00
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
#endif // _WIN32
|
|
|
|
|
2021-10-31 17:35:03 +08:00
|
|
|
static void
|
|
|
|
sdl_set_hints(const char *render_driver) {
|
2019-03-03 08:40:03 +08:00
|
|
|
|
2020-04-11 20:34:41 +08:00
|
|
|
if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) {
|
|
|
|
LOGW("Could not set render driver");
|
|
|
|
}
|
|
|
|
|
2020-02-25 19:18:49 +08:00
|
|
|
// Linear filtering
|
|
|
|
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) {
|
|
|
|
LOGW("Could not enable linear filtering");
|
2019-03-03 08:40:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Handle a click to gain focus as any other click
|
|
|
|
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
|
|
|
|
LOGW("Could not enable mouse focus clickthrough");
|
|
|
|
}
|
|
|
|
|
2021-10-31 19:40:51 +08:00
|
|
|
#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
|
|
|
|
|
2019-05-04 23:48:20 +08:00
|
|
|
#ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
|
|
|
|
// Disable compositor bypassing on X11
|
|
|
|
if (!SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0")) {
|
|
|
|
LOGW("Could not disable X11 compositor bypass");
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-05-31 21:35:53 +08:00
|
|
|
// Do not minimize on focus loss
|
|
|
|
if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
|
|
|
|
LOGW("Could not disable minimize on focus loss");
|
|
|
|
}
|
2021-10-31 17:35:03 +08:00
|
|
|
}
|
|
|
|
|
2021-10-31 19:03:35 +08:00
|
|
|
static void
|
|
|
|
sdl_configure(bool display, bool disable_screensaver) {
|
2021-10-31 17:35:03 +08:00
|
|
|
#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) {
|
2021-10-31 19:03:35 +08:00
|
|
|
return;
|
2021-10-31 17:35:03 +08:00
|
|
|
}
|
|
|
|
|
2020-06-13 20:10:41 +08:00
|
|
|
if (disable_screensaver) {
|
|
|
|
LOGD("Screensaver disabled");
|
|
|
|
SDL_DisableScreenSaver();
|
|
|
|
} else {
|
|
|
|
LOGD("Screensaver enabled");
|
|
|
|
SDL_EnableScreenSaver();
|
|
|
|
}
|
2019-03-03 08:40:03 +08:00
|
|
|
}
|
|
|
|
|
2019-03-03 06:52:22 +08:00
|
|
|
static bool
|
2022-01-22 02:26:36 +08:00
|
|
|
event_loop(struct scrcpy *s) {
|
2018-01-23 23:32:29 +08:00
|
|
|
SDL_Event event;
|
|
|
|
while (SDL_WaitEvent(&event)) {
|
2022-01-22 04:43:49 +08:00
|
|
|
switch (event.type) {
|
|
|
|
case EVENT_STREAM_STOPPED:
|
2019-11-10 04:13:20 +08:00
|
|
|
LOGW("Device disconnected");
|
2019-03-03 06:52:22 +08:00
|
|
|
return false;
|
2022-01-22 04:43:49 +08:00
|
|
|
case SDL_QUIT:
|
|
|
|
LOGD("User requested to quit");
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
sc_screen_handle_event(&s->screen, &event);
|
2018-04-29 06:17:34 +08:00
|
|
|
break;
|
2018-01-23 23:32:29 +08:00
|
|
|
}
|
|
|
|
}
|
2019-03-03 06:52:22 +08:00
|
|
|
return false;
|
2018-01-23 23:32:29 +08:00
|
|
|
}
|
|
|
|
|
2021-10-30 21:33:23 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-03-03 03:09:56 +08:00
|
|
|
static SDL_LogPriority
|
|
|
|
sdl_priority_from_av_level(int level) {
|
2019-02-09 19:06:21 +08:00
|
|
|
switch (level) {
|
|
|
|
case AV_LOG_PANIC:
|
|
|
|
case AV_LOG_FATAL:
|
|
|
|
return SDL_LOG_PRIORITY_CRITICAL;
|
|
|
|
case AV_LOG_ERROR:
|
|
|
|
return SDL_LOG_PRIORITY_ERROR;
|
|
|
|
case AV_LOG_WARNING:
|
|
|
|
return SDL_LOG_PRIORITY_WARN;
|
|
|
|
case AV_LOG_INFO:
|
|
|
|
return SDL_LOG_PRIORITY_INFO;
|
|
|
|
}
|
|
|
|
// do not forward others, which are too verbose
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
|
2019-11-26 16:08:12 +08:00
|
|
|
(void) avcl;
|
2019-02-09 19:06:21 +08:00
|
|
|
SDL_LogPriority priority = sdl_priority_from_av_level(level);
|
|
|
|
if (priority == 0) {
|
|
|
|
return;
|
|
|
|
}
|
2021-06-20 18:33:05 +08:00
|
|
|
|
|
|
|
size_t fmt_len = strlen(fmt);
|
|
|
|
char *local_fmt = malloc(fmt_len + 10);
|
2019-02-09 19:06:21 +08:00
|
|
|
if (!local_fmt) {
|
2021-11-25 05:06:11 +08:00
|
|
|
LOG_OOM();
|
2019-02-09 19:06:21 +08:00
|
|
|
return;
|
|
|
|
}
|
2021-06-20 18:33:05 +08:00
|
|
|
memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0'
|
|
|
|
memcpy(local_fmt + 9, fmt, fmt_len + 1); // include '\0'
|
2019-02-09 19:06:21 +08:00
|
|
|
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
|
2021-01-24 22:14:53 +08:00
|
|
|
free(local_fmt);
|
2019-02-09 19:06:21 +08:00
|
|
|
}
|
|
|
|
|
2021-05-16 21:32:31 +08:00
|
|
|
static void
|
|
|
|
stream_on_eos(struct stream *stream, void *userdata) {
|
|
|
|
(void) stream;
|
|
|
|
(void) userdata;
|
|
|
|
|
2021-10-31 04:36:56 +08:00
|
|
|
PUSH_EVENT(EVENT_STREAM_STOPPED);
|
2021-05-16 21:32:31 +08:00
|
|
|
}
|
|
|
|
|
2021-10-30 21:33:23 +08:00
|
|
|
static void
|
2021-11-13 06:24:12 +08:00
|
|
|
sc_server_on_connection_failed(struct sc_server *server, void *userdata) {
|
2021-10-30 21:33:23 +08:00
|
|
|
(void) server;
|
|
|
|
(void) userdata;
|
|
|
|
|
|
|
|
PUSH_EVENT(EVENT_SERVER_CONNECTION_FAILED);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2021-11-13 06:24:12 +08:00
|
|
|
sc_server_on_connected(struct sc_server *server, void *userdata) {
|
2021-10-30 21:33:23 +08:00
|
|
|
(void) server;
|
|
|
|
(void) userdata;
|
|
|
|
|
|
|
|
PUSH_EVENT(EVENT_SERVER_CONNECTED);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2021-11-13 06:24:12 +08:00
|
|
|
sc_server_on_disconnected(struct sc_server *server, void *userdata) {
|
2021-10-30 21:33:23 +08:00
|
|
|
(void) server;
|
|
|
|
(void) userdata;
|
|
|
|
|
|
|
|
LOGD("Server disconnected");
|
|
|
|
// Do nothing, the disconnection will be handled by the "stream stopped"
|
|
|
|
// event
|
|
|
|
}
|
|
|
|
|
2019-03-03 06:52:22 +08:00
|
|
|
bool
|
2021-09-10 18:57:35 +08:00
|
|
|
scrcpy(struct scrcpy_options *options) {
|
2021-05-29 03:29:14 +08:00
|
|
|
static struct scrcpy scrcpy;
|
|
|
|
struct scrcpy *s = &scrcpy;
|
|
|
|
|
2021-10-31 19:11:34 +08:00
|
|
|
// Minimal SDL initialization
|
|
|
|
if (SDL_Init(SDL_INIT_EVENTS)) {
|
|
|
|
LOGC("Could not initialize SDL: %s", SDL_GetError());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
atexit(SDL_Quit);
|
|
|
|
|
2021-01-04 05:40:33 +08:00
|
|
|
bool ret = false;
|
|
|
|
|
2021-01-01 23:34:47 +08:00
|
|
|
bool server_started = false;
|
2022-01-22 02:10:27 +08:00
|
|
|
bool file_pusher_initialized = false;
|
2021-01-01 23:34:47 +08:00
|
|
|
bool recorder_initialized = false;
|
2021-04-04 06:10:44 +08:00
|
|
|
#ifdef HAVE_V4L2
|
|
|
|
bool v4l2_sink_initialized = false;
|
|
|
|
#endif
|
2021-01-01 23:34:47 +08:00
|
|
|
bool stream_started = false;
|
2021-09-10 18:57:35 +08:00
|
|
|
#ifdef HAVE_AOA_HID
|
|
|
|
bool aoa_hid_initialized = false;
|
2022-01-02 03:00:33 +08:00
|
|
|
bool hid_keyboard_initialized = false;
|
2021-12-27 05:32:51 +08:00
|
|
|
bool hid_mouse_initialized = false;
|
2021-09-10 18:57:35 +08:00
|
|
|
#endif
|
2021-01-01 23:34:47 +08:00
|
|
|
bool controller_initialized = false;
|
|
|
|
bool controller_started = false;
|
2021-04-11 19:07:44 +08:00
|
|
|
bool screen_initialized = false;
|
2021-01-01 23:34:47 +08:00
|
|
|
|
Wait SET_CLIPBOARD ack before Ctrl+v via HID
To allow seamless copy-paste, on Ctrl+v, a SET_CLIPBOARD request is
performed before injecting Ctrl+v.
But when HID keyboard is enabled, the Ctrl+v injection is not sent on
the same channel as the clipboard request, so they are not serialized,
and may occur in any order. If Ctrl+v happens to be injected before the
new clipboard content is set, then the old content is pasted instead,
which is incorrect.
To minimize the probability of occurrence of the wrong order, a delay of
2 milliseconds was added before injecting Ctrl+v. Then 5ms. But even
with 5ms, the wrong behavior sometimes happens.
To handle it properly, add an acknowledgement mechanism, so that Ctrl+v
is injected over AOA only after the SET_CLIPBOARD request has been
performed and acknowledged by the server.
Refs e4163321f00bb3830c6049bdb6c1515e7cc668a0
Refs 45b0f8123a52f5c73a5860d616f4ceba2766ca6a
PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-22 00:40:11 +08:00
|
|
|
struct sc_acksync *acksync = NULL;
|
|
|
|
|
2021-11-13 06:24:12 +08:00
|
|
|
struct sc_server_params params = {
|
2021-05-17 15:41:22 +08:00
|
|
|
.serial = options->serial,
|
2020-05-25 03:51:40 +08:00
|
|
|
.log_level = options->log_level,
|
2019-06-05 05:59:55 +08:00
|
|
|
.crop = options->crop,
|
2019-12-10 04:16:09 +08:00
|
|
|
.port_range = options->port_range,
|
2021-11-18 08:02:53 +08:00
|
|
|
.tunnel_host = options->tunnel_host,
|
|
|
|
.tunnel_port = options->tunnel_port,
|
2019-06-05 05:59:55 +08:00
|
|
|
.max_size = options->max_size,
|
|
|
|
.bit_rate = options->bit_rate,
|
2019-11-18 05:07:19 +08:00
|
|
|
.max_fps = options->max_fps,
|
2020-02-16 19:30:36 +08:00
|
|
|
.lock_video_orientation = options->lock_video_orientation,
|
2019-06-05 03:31:46 +08:00
|
|
|
.control = options->control,
|
2020-02-24 19:16:38 +08:00
|
|
|
.display_id = options->display_id,
|
2020-05-02 05:49:37 +08:00
|
|
|
.show_touches = options->show_touches,
|
2020-05-02 07:54:48 +08:00
|
|
|
.stay_awake = options->stay_awake,
|
2020-04-26 20:22:08 +08:00
|
|
|
.codec_options = options->codec_options,
|
2020-10-12 17:23:06 +08:00
|
|
|
.encoder_name = options->encoder_name,
|
2020-05-25 05:27:34 +08:00
|
|
|
.force_adb_forward = options->force_adb_forward,
|
2021-02-21 08:42:04 +08:00
|
|
|
.power_off_on_close = options->power_off_on_close,
|
2021-11-22 15:49:10 +08:00
|
|
|
.clipboard_autosync = options->clipboard_autosync,
|
2022-01-16 06:01:14 +08:00
|
|
|
.downsize_on_error = options->downsize_on_error,
|
2021-11-26 05:22:49 +08:00
|
|
|
.tcpip = options->tcpip,
|
|
|
|
.tcpip_dst = options->tcpip_dst,
|
2019-06-05 05:59:55 +08:00
|
|
|
};
|
2021-10-28 05:40:52 +08:00
|
|
|
|
2021-11-13 06:24:12 +08:00
|
|
|
static const struct sc_server_callbacks cbs = {
|
|
|
|
.on_connection_failed = sc_server_on_connection_failed,
|
|
|
|
.on_connected = sc_server_on_connected,
|
|
|
|
.on_disconnected = sc_server_on_disconnected,
|
2021-10-30 21:33:23 +08:00
|
|
|
};
|
2021-11-13 06:24:12 +08:00
|
|
|
if (!sc_server_init(&s->server, ¶ms, &cbs, NULL)) {
|
2021-10-28 05:40:52 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-11-13 06:24:12 +08:00
|
|
|
if (!sc_server_start(&s->server)) {
|
2021-01-01 23:34:47 +08:00
|
|
|
goto end;
|
2018-01-23 23:32:29 +08:00
|
|
|
}
|
|
|
|
|
2021-01-01 23:34:47 +08:00
|
|
|
server_started = true;
|
2018-02-09 19:59:36 +08:00
|
|
|
|
2021-10-31 19:03:35 +08:00
|
|
|
if (options->display) {
|
|
|
|
sdl_set_hints(options->render_driver);
|
|
|
|
}
|
|
|
|
|
2021-10-31 19:11:34 +08:00
|
|
|
// Initialize SDL video in addition if display is enabled
|
|
|
|
if (options->display && SDL_Init(SDL_INIT_VIDEO)) {
|
2021-10-31 19:03:35 +08:00
|
|
|
LOGC("Could not initialize SDL: %s", SDL_GetError());
|
2019-05-30 17:18:54 +08:00
|
|
|
goto end;
|
Improve startup time
On startup, the client has to:
1. listen on a port
2. push and start the server to the device
3. wait for the server to connect (accept)
4. read device name and size
5. initialize SDL
6. initialize the window and renderer
7. show the window
From the execution of the app_process command to start the server on the
device, to the execution of the java main method, it takes ~800ms. As a
consequence, step 3 also takes ~800ms on the client.
Once complete, the client initializes SDL, which takes ~500ms.
These two expensive actions are executed sequentially:
HOST DEVICE
listen on port | |
push/start the server |----------------->|| app_process loads the jar
accept the connection . ^ ||
. | ||
. | WASTE ||
. | OF ||
. | TIME ||
. | ||
. | ||
. v X execution of our java main
connection accepted |<-----------------| connect to the host
init SDL || |
|| ,----------------| send frames
|| |,---------------|
|| ||,--------------|
|| |||,-------------|
|| ||||,------------|
init window/renderer | |||||,-----------|
display frames |<++++++-----------|
(many frames skipped)
The rationale for step 3 occuring before step 5 is that initializing
SDL replaces the SIGTERM handler to receive the event in the event loop,
so pressing Ctrl+C during step 5 would not work (since it blocks the
event loop).
But this is not so important; let's parallelize the SDL initialization
with the app_process execution (we'll just add a timeout to the
connection):
HOST DEVICE
listen on port | |
push/start the server |----------------->||app_process loads the jar
init SDL || ||
|| ||
|| ||
|| ||
|| ||
|| ||
accept the connection . ||
. X execution of our java main
connection accepted |<-----------------| connect to the host
init window/renderer | |
display frames |<-----------------| send frames
|<-----------------|
In addition, show the window only once the first frame is available to
avoid flickering (opening a black window for 100~200ms).
Note: the window and renderer are initialized after the connection is
accepted because they use the device information received from the
device.
2018-02-09 20:50:54 +08:00
|
|
|
}
|
|
|
|
|
2021-10-31 19:03:35 +08:00
|
|
|
sdl_configure(options->display, options->disable_screensaver);
|
|
|
|
|
2021-10-30 21:33:23 +08:00
|
|
|
// Await for server without blocking Ctrl+C handling
|
|
|
|
if (!await_for_server()) {
|
2019-05-30 17:18:54 +08:00
|
|
|
goto end;
|
2018-01-23 23:32:29 +08:00
|
|
|
}
|
|
|
|
|
2021-10-30 21:33:23 +08:00
|
|
|
// It is necessarily initialized here, since the device is connected
|
2021-11-13 06:24:12 +08:00
|
|
|
struct sc_server_info *info = &s->server.info;
|
2021-10-30 21:33:23 +08:00
|
|
|
|
2021-11-18 04:58:36 +08:00
|
|
|
const char *serial = s->server.params.serial;
|
|
|
|
assert(serial);
|
|
|
|
|
2022-01-22 02:26:36 +08:00
|
|
|
struct sc_file_pusher *fp = NULL;
|
|
|
|
|
2021-05-17 00:26:20 +08:00
|
|
|
if (options->display && options->control) {
|
2022-01-22 02:10:27 +08:00
|
|
|
if (!sc_file_pusher_init(&s->file_pusher, serial,
|
|
|
|
options->push_target)) {
|
2019-06-07 22:55:19 +08:00
|
|
|
goto end;
|
|
|
|
}
|
2022-01-22 02:26:36 +08:00
|
|
|
fp = &s->file_pusher;
|
2022-01-22 02:10:27 +08:00
|
|
|
file_pusher_initialized = true;
|
2021-04-04 06:10:44 +08:00
|
|
|
}
|
2018-04-29 06:17:34 +08:00
|
|
|
|
2021-04-04 06:10:44 +08:00
|
|
|
struct decoder *dec = NULL;
|
|
|
|
bool needs_decoder = options->display;
|
|
|
|
#ifdef HAVE_V4L2
|
|
|
|
needs_decoder |= !!options->v4l2_device;
|
|
|
|
#endif
|
|
|
|
if (needs_decoder) {
|
2021-05-29 03:29:14 +08:00
|
|
|
decoder_init(&s->decoder);
|
|
|
|
dec = &s->decoder;
|
2019-03-03 01:06:29 +08:00
|
|
|
}
|
2019-03-02 23:43:43 +08:00
|
|
|
|
2018-11-09 19:21:17 +08:00
|
|
|
struct recorder *rec = NULL;
|
2021-10-31 19:20:45 +08:00
|
|
|
if (options->record_filename) {
|
2021-05-29 03:29:14 +08:00
|
|
|
if (!recorder_init(&s->recorder,
|
2019-02-09 22:20:07 +08:00
|
|
|
options->record_filename,
|
|
|
|
options->record_format,
|
2021-10-30 21:33:23 +08:00
|
|
|
info->frame_size)) {
|
2019-05-30 17:18:54 +08:00
|
|
|
goto end;
|
2018-11-09 19:21:17 +08:00
|
|
|
}
|
2021-05-29 03:29:14 +08:00
|
|
|
rec = &s->recorder;
|
2019-05-30 17:18:54 +08:00
|
|
|
recorder_initialized = true;
|
2018-11-09 19:21:17 +08:00
|
|
|
}
|
|
|
|
|
2019-02-09 19:06:21 +08:00
|
|
|
av_log_set_callback(av_log_callback);
|
|
|
|
|
2021-08-26 18:26:44 +08:00
|
|
|
static const struct stream_callbacks stream_cbs = {
|
2021-05-16 21:32:31 +08:00
|
|
|
.on_eos = stream_on_eos,
|
|
|
|
};
|
2021-05-29 03:29:14 +08:00
|
|
|
stream_init(&s->stream, s->server.video_socket, &stream_cbs, NULL);
|
2021-04-11 21:01:05 +08:00
|
|
|
|
|
|
|
if (dec) {
|
2021-05-29 03:29:14 +08:00
|
|
|
stream_add_sink(&s->stream, &dec->packet_sink);
|
2021-04-11 21:01:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (rec) {
|
2021-05-29 03:29:14 +08:00
|
|
|
stream_add_sink(&s->stream, &rec->packet_sink);
|
2021-04-11 21:01:05 +08:00
|
|
|
}
|
2018-01-23 23:32:29 +08:00
|
|
|
|
2022-01-23 18:43:09 +08:00
|
|
|
struct sc_controller *controller = NULL;
|
2021-12-31 23:32:07 +08:00
|
|
|
struct sc_key_processor *kp = NULL;
|
|
|
|
struct sc_mouse_processor *mp = NULL;
|
|
|
|
|
2021-06-26 03:43:44 +08:00
|
|
|
if (options->control) {
|
Wait SET_CLIPBOARD ack before Ctrl+v via HID
To allow seamless copy-paste, on Ctrl+v, a SET_CLIPBOARD request is
performed before injecting Ctrl+v.
But when HID keyboard is enabled, the Ctrl+v injection is not sent on
the same channel as the clipboard request, so they are not serialized,
and may occur in any order. If Ctrl+v happens to be injected before the
new clipboard content is set, then the old content is pasted instead,
which is incorrect.
To minimize the probability of occurrence of the wrong order, a delay of
2 milliseconds was added before injecting Ctrl+v. Then 5ms. But even
with 5ms, the wrong behavior sometimes happens.
To handle it properly, add an acknowledgement mechanism, so that Ctrl+v
is injected over AOA only after the SET_CLIPBOARD request has been
performed and acknowledged by the server.
Refs e4163321f00bb3830c6049bdb6c1515e7cc668a0
Refs 45b0f8123a52f5c73a5860d616f4ceba2766ca6a
PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-22 00:40:11 +08:00
|
|
|
#ifdef HAVE_AOA_HID
|
2022-01-02 03:00:33 +08:00
|
|
|
bool use_hid_keyboard =
|
|
|
|
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID;
|
2021-12-27 05:32:51 +08:00
|
|
|
bool use_hid_mouse =
|
|
|
|
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID;
|
|
|
|
if (use_hid_keyboard || use_hid_mouse) {
|
Wait SET_CLIPBOARD ack before Ctrl+v via HID
To allow seamless copy-paste, on Ctrl+v, a SET_CLIPBOARD request is
performed before injecting Ctrl+v.
But when HID keyboard is enabled, the Ctrl+v injection is not sent on
the same channel as the clipboard request, so they are not serialized,
and may occur in any order. If Ctrl+v happens to be injected before the
new clipboard content is set, then the old content is pasted instead,
which is incorrect.
To minimize the probability of occurrence of the wrong order, a delay of
2 milliseconds was added before injecting Ctrl+v. Then 5ms. But even
with 5ms, the wrong behavior sometimes happens.
To handle it properly, add an acknowledgement mechanism, so that Ctrl+v
is injected over AOA only after the SET_CLIPBOARD request has been
performed and acknowledged by the server.
Refs e4163321f00bb3830c6049bdb6c1515e7cc668a0
Refs 45b0f8123a52f5c73a5860d616f4ceba2766ca6a
PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-22 00:40:11 +08:00
|
|
|
bool ok = sc_acksync_init(&s->acksync);
|
|
|
|
if (!ok) {
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
2022-01-02 03:00:33 +08:00
|
|
|
ok = sc_aoa_init(&s->aoa, serial, &s->acksync);
|
2021-10-17 22:58:31 +08:00
|
|
|
if (!ok) {
|
2022-01-02 03:00:33 +08:00
|
|
|
LOGE("Failed to enable HID over AOA");
|
2022-01-02 02:47:47 +08:00
|
|
|
sc_acksync_destroy(&s->acksync);
|
2021-09-10 18:57:35 +08:00
|
|
|
goto aoa_hid_end;
|
|
|
|
}
|
|
|
|
|
2022-01-02 03:00:33 +08:00
|
|
|
if (use_hid_keyboard) {
|
|
|
|
if (sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) {
|
|
|
|
hid_keyboard_initialized = true;
|
|
|
|
kp = &s->keyboard_hid.key_processor;
|
|
|
|
} else {
|
|
|
|
LOGE("Could not initialize HID keyboard");
|
|
|
|
}
|
2021-09-10 18:57:35 +08:00
|
|
|
}
|
|
|
|
|
2021-12-27 05:32:51 +08:00
|
|
|
if (use_hid_mouse) {
|
|
|
|
if (sc_hid_mouse_init(&s->mouse_hid, &s->aoa)) {
|
|
|
|
hid_mouse_initialized = true;
|
|
|
|
mp = &s->mouse_hid.mouse_processor;
|
|
|
|
} else {
|
|
|
|
LOGE("Could not initialized HID mouse");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool need_aoa = hid_keyboard_initialized || hid_mouse_initialized;
|
2022-01-02 03:00:33 +08:00
|
|
|
|
|
|
|
if (!need_aoa || !sc_aoa_start(&s->aoa)) {
|
2022-01-02 02:47:47 +08:00
|
|
|
sc_acksync_destroy(&s->acksync);
|
2021-09-10 18:57:35 +08:00
|
|
|
sc_aoa_destroy(&s->aoa);
|
|
|
|
goto aoa_hid_end;
|
|
|
|
}
|
|
|
|
|
2022-01-02 02:47:47 +08:00
|
|
|
acksync = &s->acksync;
|
2021-09-10 18:57:35 +08:00
|
|
|
|
|
|
|
aoa_hid_initialized = true;
|
|
|
|
|
|
|
|
aoa_hid_end:
|
2022-01-02 02:45:50 +08:00
|
|
|
if (!aoa_hid_initialized) {
|
2022-01-02 03:00:33 +08:00
|
|
|
if (hid_keyboard_initialized) {
|
|
|
|
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
|
|
|
hid_keyboard_initialized = false;
|
|
|
|
}
|
2021-12-27 05:32:51 +08:00
|
|
|
if (hid_mouse_initialized) {
|
|
|
|
sc_hid_mouse_destroy(&s->mouse_hid);
|
|
|
|
hid_mouse_initialized = false;
|
|
|
|
}
|
2022-01-02 03:00:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (use_hid_keyboard && !hid_keyboard_initialized) {
|
|
|
|
LOGE("Fallback to default keyboard injection method "
|
2021-09-10 18:57:35 +08:00
|
|
|
"(-K/--hid-keyboard ignored)");
|
|
|
|
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
|
|
|
|
}
|
2021-12-27 05:32:51 +08:00
|
|
|
|
|
|
|
if (use_hid_mouse && !hid_mouse_initialized) {
|
|
|
|
LOGE("Fallback to default mouse injection method "
|
|
|
|
"(-M/--hid-mouse ignored)");
|
|
|
|
options->mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT;
|
|
|
|
}
|
2022-01-02 00:28:27 +08:00
|
|
|
}
|
2021-09-10 18:57:35 +08:00
|
|
|
#else
|
2022-01-02 00:28:27 +08:00
|
|
|
assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_HID);
|
2021-12-27 05:32:51 +08:00
|
|
|
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_HID);
|
2021-09-10 18:57:35 +08:00
|
|
|
#endif
|
|
|
|
|
|
|
|
// keyboard_input_mode may have been reset if HID mode failed
|
|
|
|
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_INJECT) {
|
|
|
|
sc_keyboard_inject_init(&s->keyboard_inject, &s->controller,
|
2022-01-15 03:55:44 +08:00
|
|
|
options->key_inject_mode,
|
|
|
|
options->forward_key_repeat);
|
2021-09-10 18:57:35 +08:00
|
|
|
kp = &s->keyboard_inject.key_processor;
|
|
|
|
}
|
2021-10-03 23:44:14 +08:00
|
|
|
|
2021-12-27 05:32:51 +08:00
|
|
|
// mouse_input_mode may have been reset if HID mode failed
|
|
|
|
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_INJECT) {
|
|
|
|
sc_mouse_inject_init(&s->mouse_inject, &s->controller);
|
|
|
|
mp = &s->mouse_inject.mouse_processor;
|
|
|
|
}
|
2022-01-02 02:38:27 +08:00
|
|
|
|
2022-01-15 05:17:30 +08:00
|
|
|
if (!sc_controller_init(&s->controller, s->server.control_socket,
|
|
|
|
acksync)) {
|
2022-01-02 02:38:27 +08:00
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
controller_initialized = true;
|
|
|
|
|
2022-01-15 05:17:30 +08:00
|
|
|
if (!sc_controller_start(&s->controller)) {
|
2022-01-02 02:38:27 +08:00
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
controller_started = true;
|
2022-01-23 18:43:09 +08:00
|
|
|
controller = &s->controller;
|
2022-01-02 02:38:27 +08:00
|
|
|
|
|
|
|
if (options->turn_screen_off) {
|
2022-01-15 05:17:30 +08:00
|
|
|
struct sc_control_msg msg;
|
2022-01-27 04:31:30 +08:00
|
|
|
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
|
|
|
|
msg.set_screen_power_mode.mode = SC_SCREEN_POWER_MODE_OFF;
|
2022-01-02 02:38:27 +08:00
|
|
|
|
2022-01-15 05:17:30 +08:00
|
|
|
if (!sc_controller_push_msg(&s->controller, &msg)) {
|
2022-01-02 02:38:27 +08:00
|
|
|
LOGW("Could not request 'set screen power mode'");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-03 23:11:20 +08:00
|
|
|
}
|
|
|
|
|
2022-01-23 19:08:55 +08:00
|
|
|
// There is a controller if and only if control is enabled
|
|
|
|
assert(options->control == !!controller);
|
|
|
|
|
2021-12-31 23:32:07 +08:00
|
|
|
if (options->display) {
|
|
|
|
const char *window_title =
|
|
|
|
options->window_title ? options->window_title : info->device_name;
|
2021-12-31 23:15:41 +08:00
|
|
|
|
2022-01-15 05:17:30 +08:00
|
|
|
struct sc_screen_params screen_params = {
|
2022-01-23 18:43:09 +08:00
|
|
|
.controller = controller,
|
2022-01-22 02:26:36 +08:00
|
|
|
.fp = fp,
|
2021-12-31 23:32:07 +08:00
|
|
|
.kp = kp,
|
|
|
|
.mp = mp,
|
|
|
|
.forward_all_clicks = options->forward_all_clicks,
|
|
|
|
.legacy_paste = options->legacy_paste,
|
|
|
|
.clipboard_autosync = options->clipboard_autosync,
|
|
|
|
.shortcut_mods = &options->shortcut_mods,
|
|
|
|
.window_title = window_title,
|
|
|
|
.frame_size = info->frame_size,
|
|
|
|
.always_on_top = options->always_on_top,
|
|
|
|
.window_x = options->window_x,
|
|
|
|
.window_y = options->window_y,
|
|
|
|
.window_width = options->window_width,
|
|
|
|
.window_height = options->window_height,
|
|
|
|
.window_borderless = options->window_borderless,
|
|
|
|
.rotation = options->rotation,
|
|
|
|
.mipmaps = options->mipmaps,
|
|
|
|
.fullscreen = options->fullscreen,
|
|
|
|
.buffering_time = options->display_buffer,
|
|
|
|
};
|
|
|
|
|
2022-01-15 05:17:30 +08:00
|
|
|
if (!sc_screen_init(&s->screen, &screen_params)) {
|
2021-12-31 23:32:07 +08:00
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
screen_initialized = true;
|
|
|
|
|
|
|
|
decoder_add_sink(&s->decoder, &s->screen.frame_sink);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef HAVE_V4L2
|
|
|
|
if (options->v4l2_device) {
|
|
|
|
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device,
|
|
|
|
info->frame_size, options->v4l2_buffer)) {
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
decoder_add_sink(&s->decoder, &s->v4l2_sink.frame_sink);
|
|
|
|
|
|
|
|
v4l2_sink_initialized = true;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// now we consumed the header values, the socket receives the video stream
|
|
|
|
// start the stream
|
|
|
|
if (!stream_start(&s->stream)) {
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
stream_started = true;
|
2019-11-08 02:01:35 +08:00
|
|
|
|
2022-01-22 02:26:36 +08:00
|
|
|
ret = event_loop(s);
|
2018-02-13 17:10:18 +08:00
|
|
|
LOGD("quit...");
|
2018-03-25 21:23:00 +08:00
|
|
|
|
2021-04-14 04:22:54 +08:00
|
|
|
// Close the window immediately on closing, because screen_destroy() may
|
|
|
|
// only be called once the stream thread is joined (it may take time)
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_hide_window(&s->screen);
|
2021-04-14 04:22:54 +08:00
|
|
|
|
2019-05-30 17:18:54 +08:00
|
|
|
end:
|
2021-04-11 21:01:05 +08:00
|
|
|
// The stream is not stopped explicitly, because it will stop by itself on
|
|
|
|
// end-of-stream
|
2021-09-10 18:57:35 +08:00
|
|
|
#ifdef HAVE_AOA_HID
|
|
|
|
if (aoa_hid_initialized) {
|
2022-01-02 03:00:33 +08:00
|
|
|
if (hid_keyboard_initialized) {
|
|
|
|
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
|
|
|
}
|
2022-01-23 19:31:05 +08:00
|
|
|
if (hid_mouse_initialized) {
|
|
|
|
sc_hid_mouse_destroy(&s->mouse_hid);
|
|
|
|
}
|
2021-09-10 18:57:35 +08:00
|
|
|
sc_aoa_stop(&s->aoa);
|
|
|
|
}
|
Wait SET_CLIPBOARD ack before Ctrl+v via HID
To allow seamless copy-paste, on Ctrl+v, a SET_CLIPBOARD request is
performed before injecting Ctrl+v.
But when HID keyboard is enabled, the Ctrl+v injection is not sent on
the same channel as the clipboard request, so they are not serialized,
and may occur in any order. If Ctrl+v happens to be injected before the
new clipboard content is set, then the old content is pasted instead,
which is incorrect.
To minimize the probability of occurrence of the wrong order, a delay of
2 milliseconds was added before injecting Ctrl+v. Then 5ms. But even
with 5ms, the wrong behavior sometimes happens.
To handle it properly, add an acknowledgement mechanism, so that Ctrl+v
is injected over AOA only after the SET_CLIPBOARD request has been
performed and acknowledged by the server.
Refs e4163321f00bb3830c6049bdb6c1515e7cc668a0
Refs 45b0f8123a52f5c73a5860d616f4ceba2766ca6a
PR #2814 <https://github.com/Genymobile/scrcpy/pull/2814>
2021-11-22 00:40:11 +08:00
|
|
|
if (acksync) {
|
|
|
|
sc_acksync_destroy(acksync);
|
|
|
|
}
|
2021-09-10 18:57:35 +08:00
|
|
|
#endif
|
2019-05-30 17:18:54 +08:00
|
|
|
if (controller_started) {
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_controller_stop(&s->controller);
|
2019-05-30 17:18:54 +08:00
|
|
|
}
|
2022-01-22 02:10:27 +08:00
|
|
|
if (file_pusher_initialized) {
|
|
|
|
sc_file_pusher_stop(&s->file_pusher);
|
2019-05-30 17:18:54 +08:00
|
|
|
}
|
2021-05-17 00:26:20 +08:00
|
|
|
if (screen_initialized) {
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_interrupt(&s->screen);
|
2019-06-07 22:55:19 +08:00
|
|
|
}
|
2019-05-30 17:18:54 +08:00
|
|
|
|
2021-01-01 23:34:47 +08:00
|
|
|
if (server_started) {
|
|
|
|
// shutdown the sockets and kill the server
|
2021-11-13 06:24:12 +08:00
|
|
|
sc_server_stop(&s->server);
|
2021-01-01 23:34:47 +08:00
|
|
|
}
|
2019-05-30 17:18:54 +08:00
|
|
|
|
|
|
|
// now that the sockets are shutdown, the stream and controller are
|
|
|
|
// interrupted, we can join them
|
|
|
|
if (stream_started) {
|
2021-05-29 03:29:14 +08:00
|
|
|
stream_join(&s->stream);
|
2019-05-30 17:18:54 +08:00
|
|
|
}
|
2021-04-11 20:32:42 +08:00
|
|
|
|
2021-04-04 06:10:44 +08:00
|
|
|
#ifdef HAVE_V4L2
|
|
|
|
if (v4l2_sink_initialized) {
|
2021-05-29 03:29:14 +08:00
|
|
|
sc_v4l2_sink_destroy(&s->v4l2_sink);
|
2021-04-04 06:10:44 +08:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2021-09-10 18:57:35 +08:00
|
|
|
#ifdef HAVE_AOA_HID
|
|
|
|
if (aoa_hid_initialized) {
|
|
|
|
sc_aoa_join(&s->aoa);
|
|
|
|
sc_aoa_destroy(&s->aoa);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2021-04-11 20:32:42 +08:00
|
|
|
// Destroy the screen only after the stream is guaranteed to be finished,
|
|
|
|
// because otherwise the screen could receive new frames after destruction
|
|
|
|
if (screen_initialized) {
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_screen_join(&s->screen);
|
|
|
|
sc_screen_destroy(&s->screen);
|
2021-04-11 20:32:42 +08:00
|
|
|
}
|
|
|
|
|
2019-05-30 17:18:54 +08:00
|
|
|
if (controller_started) {
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_controller_join(&s->controller);
|
2019-03-03 01:06:29 +08:00
|
|
|
}
|
2019-05-30 17:18:54 +08:00
|
|
|
if (controller_initialized) {
|
2022-01-15 05:17:30 +08:00
|
|
|
sc_controller_destroy(&s->controller);
|
2019-03-03 01:06:29 +08:00
|
|
|
}
|
2019-05-30 17:18:54 +08:00
|
|
|
|
|
|
|
if (recorder_initialized) {
|
2021-05-29 03:29:14 +08:00
|
|
|
recorder_destroy(&s->recorder);
|
2018-11-09 19:21:17 +08:00
|
|
|
}
|
2019-05-30 17:18:54 +08:00
|
|
|
|
2022-01-22 02:10:27 +08:00
|
|
|
if (file_pusher_initialized) {
|
|
|
|
sc_file_pusher_join(&s->file_pusher);
|
|
|
|
sc_file_pusher_destroy(&s->file_pusher);
|
2019-03-03 01:06:29 +08:00
|
|
|
}
|
2019-05-30 17:18:54 +08:00
|
|
|
|
2021-11-13 06:24:12 +08:00
|
|
|
sc_server_destroy(&s->server);
|
2017-12-18 18:29:34 +08:00
|
|
|
|
2018-01-23 23:32:29 +08:00
|
|
|
return ret;
|
2017-12-12 22:12:07 +08:00
|
|
|
}
|