Compare commits

..

1 Commits

Author SHA1 Message Date
7ad8354886 Define feature test macros in common.h
This enables necessary functions once for all.

As a consequence, define common.h before any other header.
2021-01-08 19:23:02 +01:00
61 changed files with 854 additions and 1326 deletions

View File

@ -2,7 +2,6 @@ src = [
'src/main.c', 'src/main.c',
'src/adb.c', 'src/adb.c',
'src/cli.c', 'src/cli.c',
'src/compat.c',
'src/control_msg.c', 'src/control_msg.c',
'src/controller.c', 'src/controller.c',
'src/decoder.c', 'src/decoder.c',
@ -23,8 +22,7 @@ src = [
'src/video_buffer.c', 'src/video_buffer.c',
'src/util/net.c', 'src/util/net.c',
'src/util/process.c', 'src/util/process.c',
'src/util/str_util.c', 'src/util/str_util.c'
'src/util/thread.c',
] ]
if host_machine.system() == 'windows' if host_machine.system() == 'windows'
@ -33,12 +31,6 @@ else
src += [ 'src/sys/unix/process.c' ] src += [ 'src/sys/unix/process.c' ]
endif endif
check_functions = [
'strdup'
]
cc = meson.get_compiler('c')
if not get_option('crossbuild_windows') if not get_option('crossbuild_windows')
# native build # native build
@ -52,6 +44,8 @@ if not get_option('crossbuild_windows')
else else
# cross-compile mingw32 build (from Linux to Windows) # cross-compile mingw32 build (from Linux to Windows)
cc = meson.get_compiler('c')
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2') prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin' sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin'
sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/lib' sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/lib'
@ -86,18 +80,16 @@ else
endif endif
cc = meson.get_compiler('c')
if host_machine.system() == 'windows' if host_machine.system() == 'windows'
dependencies += cc.find_library('ws2_32') dependencies += cc.find_library('ws2_32')
endif endif
conf = configuration_data() conf = configuration_data()
foreach f : check_functions # expose the build type
if cc.has_function(f) conf.set('NDEBUG', get_option('buildtype') != 'debug')
define = 'HAVE_' + f.underscorify().to_upper()
conf.set(define, true)
endif
endforeach
# the version, updated on release # the version, updated on release
conf.set_quoted('SCRCPY_VERSION', meson.project_version()) conf.set_quoted('SCRCPY_VERSION', meson.project_version())
@ -114,10 +106,23 @@ conf.set('PORTABLE', get_option('portable'))
conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183') conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183')
conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199') conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
# the default max video size for both dimensions, in pixels
# overridden by option --max-size
conf.set('DEFAULT_MAX_SIZE', '0') # 0: unlimited
# the default video orientation
# natural device orientation is 0 and each increment adds 90 degrees
# counterclockwise
# overridden by option --lock-video-orientation
conf.set('DEFAULT_LOCK_VIDEO_ORIENTATION', '-1') # -1: unlocked
# the default video bitrate, in bits/second # the default video bitrate, in bits/second
# overridden by option --bit-rate # overridden by option --bit-rate
conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps
# enable High DPI support
conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
# run a server debugger and wait for a client to be attached # run a server debugger and wait for a client to be attached
conf.set('SERVER_DEBUGGER', get_option('server_debugger')) conf.set('SERVER_DEBUGGER', get_option('server_debugger'))

View File

@ -187,16 +187,16 @@ Enable "show touches" on start, restore the initial value on exit.
It only shows physical touches (not clicks from scrcpy). It only shows physical touches (not clicks from scrcpy).
.TP
.B \-v, \-\-version
Print the version of scrcpy.
.TP .TP
.BI "\-V, \-\-verbosity " value .BI "\-V, \-\-verbosity " value
Set the log level ("debug", "info", "warn" or "error"). Set the log level ("debug", "info", "warn" or "error").
Default is "info" for release builds, "debug" for debug builds. Default is "info" for release builds, "debug" for debug builds.
.TP
.B \-v, \-\-version
Print the version of scrcpy.
.TP .TP
.B \-w, \-\-stay-awake .B \-w, \-\-stay-awake
Keep the device on while scrcpy is running, when the device is plugged in. Keep the device on while scrcpy is running, when the device is plugged in.

View File

@ -173,7 +173,7 @@ adb_push(const char *serial, const char *local, const char *remote) {
} }
remote = strquote(remote); remote = strquote(remote);
if (!remote) { if (!remote) {
free((void *) local); SDL_free((void *) local);
return PROCESS_NONE; return PROCESS_NONE;
} }
#endif #endif
@ -182,8 +182,8 @@ adb_push(const char *serial, const char *local, const char *remote) {
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__ #ifdef __WINDOWS__
free((void *) remote); SDL_free((void *) remote);
free((void *) local); SDL_free((void *) local);
#endif #endif
return proc; return proc;
@ -204,7 +204,7 @@ adb_install(const char *serial, const char *local) {
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__ #ifdef __WINDOWS__
free((void *) local); SDL_free((void *) local);
#endif #endif
return proc; return proc;

View File

@ -10,9 +10,6 @@
#include "util/log.h" #include "util/log.h"
#include "util/str_util.h" #include "util/str_util.h"
#define STR_IMPL_(x) #x
#define STR(x) STR_IMPL_(x)
void void
scrcpy_print_usage(const char *arg0) { scrcpy_print_usage(const char *arg0) {
fprintf(stderr, fprintf(stderr,
@ -26,7 +23,7 @@ scrcpy_print_usage(const char *arg0) {
" -b, --bit-rate value\n" " -b, --bit-rate value\n"
" Encode the video at the given bit-rate, expressed in bits/s.\n" " Encode the video at the given bit-rate, expressed in bits/s.\n"
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" " Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
" Default is " STR(DEFAULT_BIT_RATE) ".\n" " Default is %d.\n"
"\n" "\n"
" --codec-options key[:type]=value[,...]\n" " --codec-options key[:type]=value[,...]\n"
" Set a list of comma-separated key:type=value options for the\n" " Set a list of comma-separated key:type=value options for the\n"
@ -84,7 +81,7 @@ scrcpy_print_usage(const char *arg0) {
" Possible values are -1 (unlocked), 0, 1, 2 and 3.\n" " Possible values are -1 (unlocked), 0, 1, 2 and 3.\n"
" Natural device orientation is 0, and each increment adds a\n" " Natural device orientation is 0, and each increment adds a\n"
" 90 degrees rotation counterclockwise.\n" " 90 degrees rotation counterclockwise.\n"
" Default is -1 (unlocked).\n" " Default is %d%s.\n"
"\n" "\n"
" --max-fps value\n" " --max-fps value\n"
" Limit the frame rate of screen capture (officially supported\n" " Limit the frame rate of screen capture (officially supported\n"
@ -94,7 +91,7 @@ scrcpy_print_usage(const char *arg0) {
" Limit both the width and height of the video to value. The\n" " Limit both the width and height of the video to value. The\n"
" other dimension is computed so that the device aspect-ratio\n" " other dimension is computed so that the device aspect-ratio\n"
" is preserved.\n" " is preserved.\n"
" Default is 0 (unlimited).\n" " Default is %d%s.\n"
"\n" "\n"
" -n, --no-control\n" " -n, --no-control\n"
" Disable device control (mirror the device in read-only).\n" " Disable device control (mirror the device in read-only).\n"
@ -113,8 +110,7 @@ scrcpy_print_usage(const char *arg0) {
"\n" "\n"
" -p, --port port[:port]\n" " -p, --port port[:port]\n"
" Set the TCP port (range) used by the client to listen.\n" " Set the TCP port (range) used by the client to listen.\n"
" Default is " STR(DEFAULT_LOCAL_PORT_RANGE_FIRST) ":" " Default is %d:%d.\n"
STR(DEFAULT_LOCAL_PORT_RANGE_LAST) ".\n"
"\n" "\n"
" --prefer-text\n" " --prefer-text\n"
" Inject alpha characters and space as text events instead of\n" " Inject alpha characters and space as text events instead of\n"
@ -179,6 +175,9 @@ scrcpy_print_usage(const char *arg0) {
" on exit.\n" " on exit.\n"
" It only shows physical touches (not clicks from scrcpy).\n" " It only shows physical touches (not clicks from scrcpy).\n"
"\n" "\n"
" -v, --version\n"
" Print the version of scrcpy.\n"
"\n"
" -V, --verbosity value\n" " -V, --verbosity value\n"
" Set the log level (debug, info, warn or error).\n" " Set the log level (debug, info, warn or error).\n"
#ifndef NDEBUG #ifndef NDEBUG
@ -186,9 +185,6 @@ scrcpy_print_usage(const char *arg0) {
#else #else
" Default is info.\n" " Default is info.\n"
#endif #endif
"\n"
" -v, --version\n"
" Print the version of scrcpy.\n"
"\n" "\n"
" -w, --stay-awake\n" " -w, --stay-awake\n"
" Keep the device on while scrcpy is running, when the device\n" " Keep the device on while scrcpy is running, when the device\n"
@ -301,7 +297,12 @@ scrcpy_print_usage(const char *arg0) {
"\n" "\n"
" Drag & drop APK file\n" " Drag & drop APK file\n"
" Install APK from computer\n" " Install APK from computer\n"
"\n", arg0); "\n",
arg0,
DEFAULT_BIT_RATE,
DEFAULT_LOCK_VIDEO_ORIENTATION, DEFAULT_LOCK_VIDEO_ORIENTATION >= 0 ? "" : " (unlocked)",
DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
DEFAULT_LOCAL_PORT_RANGE_FIRST, DEFAULT_LOCAL_PORT_RANGE_LAST);
} }
static bool static bool
@ -666,7 +667,6 @@ guess_record_format(const char *filename) {
#define OPT_FORWARD_ALL_CLICKS 1023 #define OPT_FORWARD_ALL_CLICKS 1023
#define OPT_LEGACY_PASTE 1024 #define OPT_LEGACY_PASTE 1024
#define OPT_ENCODER_NAME 1025 #define OPT_ENCODER_NAME 1025
#define OPT_POWER_OFF_ON_CLOSE 1026
bool bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
@ -717,8 +717,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT}, {"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT},
{"window-borderless", no_argument, NULL, {"window-borderless", no_argument, NULL,
OPT_WINDOW_BORDERLESS}, OPT_WINDOW_BORDERLESS},
{"power-off-on-close", no_argument, NULL,
OPT_POWER_OFF_ON_CLOSE},
{NULL, 0, NULL, 0 }, {NULL, 0, NULL, 0 },
}; };
@ -887,9 +885,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case OPT_LEGACY_PASTE: case OPT_LEGACY_PASTE:
opts->legacy_paste = true; opts->legacy_paste = true;
break; break;
case OPT_POWER_OFF_ON_CLOSE:
opts->power_off_on_close = true;
break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
return false; return false;

View File

@ -1,14 +0,0 @@
#include "compat.h"
#include "config.h"
#ifndef HAVE_STRDUP
char *strdup(const char *s) {
size_t size = strlen(s) + 1;
char *dup = malloc(size);
if (dup) {
memcpy(dup, s, size);
}
return dup;
}
#endif

View File

@ -56,8 +56,4 @@
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR # define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
#endif #endif
#ifndef HAVE_STRDUP
char *strdup(const char *s);
#endif
#endif #endif

View File

@ -1,7 +1,6 @@
#include "control_msg.h" #include "control_msg.h"
#include <assert.h> #include <assert.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include "util/buffer_util.h" #include "util/buffer_util.h"
@ -67,9 +66,6 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
buffer_write32be(&buf[17], buffer_write32be(&buf[17],
(uint32_t) msg->inject_scroll_event.vscroll); (uint32_t) msg->inject_scroll_event.vscroll);
return 21; return 21;
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
buf[1] = msg->inject_keycode.action;
return 2;
case CONTROL_MSG_TYPE_SET_CLIPBOARD: { case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
buf[1] = !!msg->set_clipboard.paste; buf[1] = !!msg->set_clipboard.paste;
size_t len = write_string(msg->set_clipboard.text, size_t len = write_string(msg->set_clipboard.text,
@ -80,9 +76,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE: case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
buf[1] = msg->set_screen_power_mode.mode; buf[1] = msg->set_screen_power_mode.mode;
return 2; return 2;
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL: case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL: case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL:
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
case CONTROL_MSG_TYPE_GET_CLIPBOARD: case CONTROL_MSG_TYPE_GET_CLIPBOARD:
case CONTROL_MSG_TYPE_ROTATE_DEVICE: case CONTROL_MSG_TYPE_ROTATE_DEVICE:
// no additional data // no additional data
@ -97,10 +93,10 @@ void
control_msg_destroy(struct control_msg *msg) { control_msg_destroy(struct control_msg *msg) {
switch (msg->type) { switch (msg->type) {
case CONTROL_MSG_TYPE_INJECT_TEXT: case CONTROL_MSG_TYPE_INJECT_TEXT:
free(msg->inject_text.text); SDL_free(msg->inject_text.text);
break; break;
case CONTROL_MSG_TYPE_SET_CLIPBOARD: case CONTROL_MSG_TYPE_SET_CLIPBOARD:
free(msg->set_clipboard.text); SDL_free(msg->set_clipboard.text);
break; break;
default: default:
// do nothing // do nothing

View File

@ -27,8 +27,7 @@ enum control_msg_type {
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT, CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL, CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL, CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
CONTROL_MSG_TYPE_COLLAPSE_PANELS,
CONTROL_MSG_TYPE_GET_CLIPBOARD, CONTROL_MSG_TYPE_GET_CLIPBOARD,
CONTROL_MSG_TYPE_SET_CLIPBOARD, CONTROL_MSG_TYPE_SET_CLIPBOARD,
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE, CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
@ -51,7 +50,7 @@ struct control_msg {
enum android_metastate metastate; enum android_metastate metastate;
} inject_keycode; } inject_keycode;
struct { struct {
char *text; // owned, to be freed by free() char *text; // owned, to be freed by SDL_free()
} inject_text; } inject_text;
struct { struct {
enum android_motionevent_action action; enum android_motionevent_action action;
@ -66,11 +65,7 @@ struct control_msg {
int32_t vscroll; int32_t vscroll;
} inject_scroll_event; } inject_scroll_event;
struct { struct {
enum android_keyevent_action action; // action for the BACK key char *text; // owned, to be freed by SDL_free()
// screen may only be turned on on ACTION_DOWN
} back_or_screen_on;
struct {
char *text; // owned, to be freed by free()
bool paste; bool paste;
} set_clipboard; } set_clipboard;
struct { struct {

View File

@ -2,27 +2,25 @@
#include <assert.h> #include <assert.h>
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
bool bool
controller_init(struct controller *controller, socket_t control_socket) { controller_init(struct controller *controller, socket_t control_socket) {
cbuf_init(&controller->queue); cbuf_init(&controller->queue);
bool ok = receiver_init(&controller->receiver, control_socket); if (!receiver_init(&controller->receiver, control_socket)) {
if (!ok) {
return false; return false;
} }
ok = sc_mutex_init(&controller->mutex); if (!(controller->mutex = SDL_CreateMutex())) {
if (!ok) {
receiver_destroy(&controller->receiver); receiver_destroy(&controller->receiver);
return false; return false;
} }
ok = sc_cond_init(&controller->msg_cond); if (!(controller->msg_cond = SDL_CreateCond())) {
if (!ok) {
receiver_destroy(&controller->receiver); receiver_destroy(&controller->receiver);
sc_mutex_destroy(&controller->mutex); SDL_DestroyMutex(controller->mutex);
return false; return false;
} }
@ -34,8 +32,8 @@ controller_init(struct controller *controller, socket_t control_socket) {
void void
controller_destroy(struct controller *controller) { controller_destroy(struct controller *controller) {
sc_cond_destroy(&controller->msg_cond); SDL_DestroyCond(controller->msg_cond);
sc_mutex_destroy(&controller->mutex); SDL_DestroyMutex(controller->mutex);
struct control_msg msg; struct control_msg msg;
while (cbuf_take(&controller->queue, &msg)) { while (cbuf_take(&controller->queue, &msg)) {
@ -48,13 +46,13 @@ controller_destroy(struct controller *controller) {
bool bool
controller_push_msg(struct controller *controller, controller_push_msg(struct controller *controller,
const struct control_msg *msg) { const struct control_msg *msg) {
sc_mutex_lock(&controller->mutex); mutex_lock(controller->mutex);
bool was_empty = cbuf_is_empty(&controller->queue); bool was_empty = cbuf_is_empty(&controller->queue);
bool res = cbuf_push(&controller->queue, *msg); bool res = cbuf_push(&controller->queue, *msg);
if (was_empty) { if (was_empty) {
sc_cond_signal(&controller->msg_cond); cond_signal(controller->msg_cond);
} }
sc_mutex_unlock(&controller->mutex); mutex_unlock(controller->mutex);
return res; return res;
} }
@ -62,12 +60,12 @@ static bool
process_msg(struct controller *controller, process_msg(struct controller *controller,
const struct control_msg *msg) { const struct control_msg *msg) {
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE]; static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
size_t length = control_msg_serialize(msg, serialized_msg); int length = control_msg_serialize(msg, serialized_msg);
if (!length) { if (!length) {
return false; return false;
} }
int w = net_send_all(controller->control_socket, serialized_msg, length); int w = net_send_all(controller->control_socket, serialized_msg, length);
return (size_t) w == length; return w == length;
} }
static int static int
@ -75,20 +73,20 @@ run_controller(void *data) {
struct controller *controller = data; struct controller *controller = data;
for (;;) { for (;;) {
sc_mutex_lock(&controller->mutex); mutex_lock(controller->mutex);
while (!controller->stopped && cbuf_is_empty(&controller->queue)) { while (!controller->stopped && cbuf_is_empty(&controller->queue)) {
sc_cond_wait(&controller->msg_cond, &controller->mutex); cond_wait(controller->msg_cond, controller->mutex);
} }
if (controller->stopped) { if (controller->stopped) {
// stop immediately, do not process further msgs // stop immediately, do not process further msgs
sc_mutex_unlock(&controller->mutex); mutex_unlock(controller->mutex);
break; break;
} }
struct control_msg msg; struct control_msg msg;
bool non_empty = cbuf_take(&controller->queue, &msg); bool non_empty = cbuf_take(&controller->queue, &msg);
assert(non_empty); assert(non_empty);
(void) non_empty; (void) non_empty;
sc_mutex_unlock(&controller->mutex); mutex_unlock(controller->mutex);
bool ok = process_msg(controller, &msg); bool ok = process_msg(controller, &msg);
control_msg_destroy(&msg); control_msg_destroy(&msg);
@ -104,16 +102,16 @@ bool
controller_start(struct controller *controller) { controller_start(struct controller *controller) {
LOGD("Starting controller thread"); LOGD("Starting controller thread");
bool ok = sc_thread_create(&controller->thread, run_controller, controller->thread = SDL_CreateThread(run_controller, "controller",
"controller", controller); controller);
if (!ok) { if (!controller->thread) {
LOGC("Could not start controller thread"); LOGC("Could not start controller thread");
return false; return false;
} }
if (!receiver_start(&controller->receiver)) { if (!receiver_start(&controller->receiver)) {
controller_stop(controller); controller_stop(controller);
sc_thread_join(&controller->thread, NULL); SDL_WaitThread(controller->thread, NULL);
return false; return false;
} }
@ -122,14 +120,14 @@ controller_start(struct controller *controller) {
void void
controller_stop(struct controller *controller) { controller_stop(struct controller *controller) {
sc_mutex_lock(&controller->mutex); mutex_lock(controller->mutex);
controller->stopped = true; controller->stopped = true;
sc_cond_signal(&controller->msg_cond); cond_signal(controller->msg_cond);
sc_mutex_unlock(&controller->mutex); mutex_unlock(controller->mutex);
} }
void void
controller_join(struct controller *controller) { controller_join(struct controller *controller) {
sc_thread_join(&controller->thread, NULL); SDL_WaitThread(controller->thread, NULL);
receiver_join(&controller->receiver); receiver_join(&controller->receiver);
} }

View File

@ -4,20 +4,21 @@
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "control_msg.h" #include "control_msg.h"
#include "receiver.h" #include "receiver.h"
#include "util/cbuf.h" #include "util/cbuf.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h"
struct control_msg_queue CBUF(struct control_msg, 64); struct control_msg_queue CBUF(struct control_msg, 64);
struct controller { struct controller {
socket_t control_socket; socket_t control_socket;
sc_thread thread; SDL_Thread *thread;
sc_mutex mutex; SDL_mutex *mutex;
sc_cond msg_cond; SDL_cond *msg_cond;
bool stopped; bool stopped;
struct control_msg_queue queue; struct control_msg_queue queue;
struct receiver receiver; struct receiver receiver;

View File

@ -1,11 +1,34 @@
#include "decoder.h" #include "decoder.h"
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include <unistd.h>
#include "events.h" #include "events.h"
#include "recorder.h"
#include "video_buffer.h" #include "video_buffer.h"
#include "util/buffer_util.h"
#include "util/log.h" #include "util/log.h"
// set the decoded frame as ready for rendering, and notify
static void
push_frame(struct decoder *decoder) {
bool previous_frame_skipped;
video_buffer_offer_decoded_frame(decoder->video_buffer,
&previous_frame_skipped);
if (previous_frame_skipped) {
// the previous EVENT_NEW_FRAME will consume this frame
return;
}
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
SDL_PushEvent(&new_frame_event);
}
void void
decoder_init(struct decoder *decoder, struct video_buffer *vb) { decoder_init(struct decoder *decoder, struct video_buffer *vb) {
decoder->video_buffer = vb; decoder->video_buffer = vb;
@ -45,10 +68,10 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) {
return false; return false;
} }
ret = avcodec_receive_frame(decoder->codec_ctx, ret = avcodec_receive_frame(decoder->codec_ctx,
decoder->video_buffer->producer_frame); decoder->video_buffer->decoding_frame);
if (!ret) { if (!ret) {
// a frame was received // a frame was received
video_buffer_producer_offer_frame(decoder->video_buffer); push_frame(decoder);
} else if (ret != AVERROR(EAGAIN)) { } else if (ret != AVERROR(EAGAIN)) {
LOGE("Could not receive video frame: %d", ret); LOGE("Could not receive video frame: %d", ret);
return false; return false;
@ -56,7 +79,7 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) {
#else #else
int got_picture; int got_picture;
int len = avcodec_decode_video2(decoder->codec_ctx, int len = avcodec_decode_video2(decoder->codec_ctx,
decoder->video_buffer->producer_frame, decoder->video_buffer->decoding_frame,
&got_picture, &got_picture,
packet); packet);
if (len < 0) { if (len < 0) {
@ -64,7 +87,7 @@ decoder_push(struct decoder *decoder, const AVPacket *packet) {
return false; return false;
} }
if (got_picture) { if (got_picture) {
video_buffer_producer_offer_frame(decoder->video_buffer); push_frame(decoder);
} }
#endif #endif
return true; return true;

View File

@ -10,7 +10,6 @@ struct video_buffer;
struct decoder { struct decoder {
struct video_buffer *video_buffer; struct video_buffer *video_buffer;
AVCodecContext *codec_ctx; AVCodecContext *codec_ctx;
}; };

View File

@ -1,6 +1,5 @@
#include "device_msg.h" #include "device_msg.h"
#include <stdlib.h>
#include <string.h> #include <string.h>
#include "util/buffer_util.h" #include "util/buffer_util.h"
@ -21,7 +20,7 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
if (clipboard_len > len - 5) { if (clipboard_len > len - 5) {
return 0; // not available return 0; // not available
} }
char *text = malloc(clipboard_len + 1); char *text = SDL_malloc(clipboard_len + 1);
if (!text) { if (!text) {
LOGW("Could not allocate text for clipboard"); LOGW("Could not allocate text for clipboard");
return -1; return -1;
@ -43,6 +42,6 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
void void
device_msg_destroy(struct device_msg *msg) { device_msg_destroy(struct device_msg *msg) {
if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) { if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) {
free(msg->clipboard.text); SDL_free(msg->clipboard.text);
} }
} }

View File

@ -19,7 +19,7 @@ struct device_msg {
enum device_msg_type type; enum device_msg_type type;
union { union {
struct { struct {
char *text; // owned, to be freed by free() char *text; // owned, to be freed by SDL_free()
} clipboard; } clipboard;
}; };
}; };

View File

@ -1,2 +1,3 @@
#define EVENT_NEW_FRAME SDL_USEREVENT #define EVENT_NEW_SESSION SDL_USEREVENT
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1) #define EVENT_NEW_FRAME (SDL_USEREVENT + 1)
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 2)

View File

@ -4,13 +4,14 @@
#include <string.h> #include <string.h>
#include "adb.h" #include "adb.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#define DEFAULT_PUSH_TARGET "/sdcard/" #define DEFAULT_PUSH_TARGET "/sdcard/"
static void static void
file_handler_request_destroy(struct file_handler_request *req) { file_handler_request_destroy(struct file_handler_request *req) {
free(req->file); SDL_free(req->file);
} }
bool bool
@ -19,23 +20,21 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
cbuf_init(&file_handler->queue); cbuf_init(&file_handler->queue);
bool ok = sc_mutex_init(&file_handler->mutex); if (!(file_handler->mutex = SDL_CreateMutex())) {
if (!ok) {
return false; return false;
} }
ok = sc_cond_init(&file_handler->event_cond); if (!(file_handler->event_cond = SDL_CreateCond())) {
if (!ok) { SDL_DestroyMutex(file_handler->mutex);
sc_mutex_destroy(&file_handler->mutex);
return false; return false;
} }
if (serial) { if (serial) {
file_handler->serial = strdup(serial); file_handler->serial = SDL_strdup(serial);
if (!file_handler->serial) { if (!file_handler->serial) {
LOGW("Could not strdup serial"); LOGW("Could not strdup serial");
sc_cond_destroy(&file_handler->event_cond); SDL_DestroyCond(file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex); SDL_DestroyMutex(file_handler->mutex);
return false; return false;
} }
} else { } else {
@ -55,9 +54,9 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
void void
file_handler_destroy(struct file_handler *file_handler) { file_handler_destroy(struct file_handler *file_handler) {
sc_cond_destroy(&file_handler->event_cond); SDL_DestroyCond(file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex); SDL_DestroyMutex(file_handler->mutex);
free(file_handler->serial); SDL_free(file_handler->serial);
struct file_handler_request req; struct file_handler_request req;
while (cbuf_take(&file_handler->queue, &req)) { while (cbuf_take(&file_handler->queue, &req)) {
@ -93,13 +92,13 @@ file_handler_request(struct file_handler *file_handler,
.file = file, .file = file,
}; };
sc_mutex_lock(&file_handler->mutex); mutex_lock(file_handler->mutex);
bool was_empty = cbuf_is_empty(&file_handler->queue); bool was_empty = cbuf_is_empty(&file_handler->queue);
bool res = cbuf_push(&file_handler->queue, req); bool res = cbuf_push(&file_handler->queue, req);
if (was_empty) { if (was_empty) {
sc_cond_signal(&file_handler->event_cond); cond_signal(file_handler->event_cond);
} }
sc_mutex_unlock(&file_handler->mutex); mutex_unlock(file_handler->mutex);
return res; return res;
} }
@ -108,14 +107,14 @@ run_file_handler(void *data) {
struct file_handler *file_handler = data; struct file_handler *file_handler = data;
for (;;) { for (;;) {
sc_mutex_lock(&file_handler->mutex); mutex_lock(file_handler->mutex);
file_handler->current_process = PROCESS_NONE; file_handler->current_process = PROCESS_NONE;
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) { while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
sc_cond_wait(&file_handler->event_cond, &file_handler->mutex); cond_wait(file_handler->event_cond, file_handler->mutex);
} }
if (file_handler->stopped) { if (file_handler->stopped) {
// stop immediately, do not process further events // stop immediately, do not process further events
sc_mutex_unlock(&file_handler->mutex); mutex_unlock(file_handler->mutex);
break; break;
} }
struct file_handler_request req; struct file_handler_request req;
@ -133,16 +132,16 @@ run_file_handler(void *data) {
file_handler->push_target); file_handler->push_target);
} }
file_handler->current_process = process; file_handler->current_process = process;
sc_mutex_unlock(&file_handler->mutex); mutex_unlock(file_handler->mutex);
if (req.action == ACTION_INSTALL_APK) { if (req.action == ACTION_INSTALL_APK) {
if (process_check_success(process, "adb install", false)) { if (process_check_success(process, "adb install")) {
LOGI("%s successfully installed", req.file); LOGI("%s successfully installed", req.file);
} else { } else {
LOGE("Failed to install %s", req.file); LOGE("Failed to install %s", req.file);
} }
} else { } else {
if (process_check_success(process, "adb push", false)) { if (process_check_success(process, "adb push")) {
LOGI("%s successfully pushed to %s", req.file, LOGI("%s successfully pushed to %s", req.file,
file_handler->push_target); file_handler->push_target);
} else { } else {
@ -151,14 +150,6 @@ run_file_handler(void *data) {
} }
} }
sc_mutex_lock(&file_handler->mutex);
// Close the process (it is necessary already terminated)
// Execute this call with mutex locked to avoid race conditions with
// file_handler_stop()
process_close(file_handler->current_process);
file_handler->current_process = PROCESS_NONE;
sc_mutex_unlock(&file_handler->mutex);
file_handler_request_destroy(&req); file_handler_request_destroy(&req);
} }
return 0; return 0;
@ -168,9 +159,9 @@ bool
file_handler_start(struct file_handler *file_handler) { file_handler_start(struct file_handler *file_handler) {
LOGD("Starting file_handler thread"); LOGD("Starting file_handler thread");
bool ok = sc_thread_create(&file_handler->thread, run_file_handler, file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler",
"file_handler", file_handler); file_handler);
if (!ok) { if (!file_handler->thread) {
LOGC("Could not start file_handler thread"); LOGC("Could not start file_handler thread");
return false; return false;
} }
@ -180,18 +171,20 @@ file_handler_start(struct file_handler *file_handler) {
void void
file_handler_stop(struct file_handler *file_handler) { file_handler_stop(struct file_handler *file_handler) {
sc_mutex_lock(&file_handler->mutex); mutex_lock(file_handler->mutex);
file_handler->stopped = true; file_handler->stopped = true;
sc_cond_signal(&file_handler->event_cond); cond_signal(file_handler->event_cond);
if (file_handler->current_process != PROCESS_NONE) { if (file_handler->current_process != PROCESS_NONE) {
if (!process_terminate(file_handler->current_process)) { if (!process_terminate(file_handler->current_process)) {
LOGW("Could not terminate push/install process"); LOGW("Could not terminate install process");
} }
process_wait(file_handler->current_process, NULL);
file_handler->current_process = PROCESS_NONE;
} }
sc_mutex_unlock(&file_handler->mutex); mutex_unlock(file_handler->mutex);
} }
void void
file_handler_join(struct file_handler *file_handler) { file_handler_join(struct file_handler *file_handler) {
sc_thread_join(&file_handler->thread, NULL); SDL_WaitThread(file_handler->thread, NULL);
} }

View File

@ -4,10 +4,11 @@
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "adb.h" #include "adb.h"
#include "util/cbuf.h" #include "util/cbuf.h"
#include "util/thread.h"
typedef enum { typedef enum {
ACTION_INSTALL_APK, ACTION_INSTALL_APK,
@ -24,9 +25,9 @@ struct file_handler_request_queue CBUF(struct file_handler_request, 16);
struct file_handler { struct file_handler {
char *serial; char *serial;
const char *push_target; const char *push_target;
sc_thread thread; SDL_Thread *thread;
sc_mutex mutex; SDL_mutex *mutex;
sc_cond event_cond; SDL_cond *event_cond;
bool stopped; bool stopped;
bool initialized; bool initialized;
process_t current_process; process_t current_process;
@ -49,7 +50,7 @@ file_handler_stop(struct file_handler *file_handler);
void void
file_handler_join(struct file_handler *file_handler); file_handler_join(struct file_handler *file_handler);
// take ownership of file, and will free() it // take ownership of file, and will SDL_free() it
bool bool
file_handler_request(struct file_handler *file_handler, file_handler_request(struct file_handler *file_handler,
file_handler_action_t action, file_handler_action_t action,

View File

@ -3,24 +3,25 @@
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_timer.h> #include <SDL2/SDL_timer.h>
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#define FPS_COUNTER_INTERVAL_MS 1000 #define FPS_COUNTER_INTERVAL_MS 1000
bool bool
fps_counter_init(struct fps_counter *counter) { fps_counter_init(struct fps_counter *counter) {
bool ok = sc_mutex_init(&counter->mutex); counter->mutex = SDL_CreateMutex();
if (!ok) { if (!counter->mutex) {
return false; return false;
} }
ok = sc_cond_init(&counter->state_cond); counter->state_cond = SDL_CreateCond();
if (!ok) { if (!counter->state_cond) {
sc_mutex_destroy(&counter->mutex); SDL_DestroyMutex(counter->mutex);
return false; return false;
} }
counter->thread_started = false; counter->thread = NULL;
atomic_init(&counter->started, 0); atomic_init(&counter->started, 0);
// no need to initialize the other fields, they are unused until started // no need to initialize the other fields, they are unused until started
@ -29,8 +30,8 @@ fps_counter_init(struct fps_counter *counter) {
void void
fps_counter_destroy(struct fps_counter *counter) { fps_counter_destroy(struct fps_counter *counter) {
sc_cond_destroy(&counter->state_cond); SDL_DestroyCond(counter->state_cond);
sc_mutex_destroy(&counter->mutex); SDL_DestroyMutex(counter->mutex);
} }
static inline bool static inline bool
@ -76,10 +77,10 @@ static int
run_fps_counter(void *data) { run_fps_counter(void *data) {
struct fps_counter *counter = data; struct fps_counter *counter = data;
sc_mutex_lock(&counter->mutex); mutex_lock(counter->mutex);
while (!counter->interrupted) { while (!counter->interrupted) {
while (!counter->interrupted && !is_started(counter)) { while (!counter->interrupted && !is_started(counter)) {
sc_cond_wait(&counter->state_cond, &counter->mutex); cond_wait(counter->state_cond, counter->mutex);
} }
while (!counter->interrupted && is_started(counter)) { while (!counter->interrupted && is_started(counter)) {
uint32_t now = SDL_GetTicks(); uint32_t now = SDL_GetTicks();
@ -89,35 +90,32 @@ run_fps_counter(void *data) {
uint32_t remaining = counter->next_timestamp - now; uint32_t remaining = counter->next_timestamp - now;
// ignore the reason (timeout or signaled), we just loop anyway // ignore the reason (timeout or signaled), we just loop anyway
sc_cond_timedwait(&counter->state_cond, &counter->mutex, remaining); cond_wait_timeout(counter->state_cond, counter->mutex, remaining);
} }
} }
sc_mutex_unlock(&counter->mutex); mutex_unlock(counter->mutex);
return 0; return 0;
} }
bool bool
fps_counter_start(struct fps_counter *counter) { fps_counter_start(struct fps_counter *counter) {
sc_mutex_lock(&counter->mutex); mutex_lock(counter->mutex);
counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS; counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS;
counter->nr_rendered = 0; counter->nr_rendered = 0;
counter->nr_skipped = 0; counter->nr_skipped = 0;
sc_mutex_unlock(&counter->mutex); mutex_unlock(counter->mutex);
set_started(counter, true); set_started(counter, true);
sc_cond_signal(&counter->state_cond); cond_signal(counter->state_cond);
// counter->thread_started and counter->thread are always accessed from the // counter->thread is always accessed from the same thread, no need to lock
// same thread, no need to lock if (!counter->thread) {
if (!counter->thread_started) { counter->thread =
bool ok = sc_thread_create(&counter->thread, run_fps_counter, SDL_CreateThread(run_fps_counter, "fps counter", counter);
"fps counter", counter); if (!counter->thread) {
if (!ok) {
LOGE("Could not start FPS counter thread"); LOGE("Could not start FPS counter thread");
return false; return false;
} }
counter->thread_started = true;
} }
return true; return true;
@ -126,7 +124,7 @@ fps_counter_start(struct fps_counter *counter) {
void void
fps_counter_stop(struct fps_counter *counter) { fps_counter_stop(struct fps_counter *counter) {
set_started(counter, false); set_started(counter, false);
sc_cond_signal(&counter->state_cond); cond_signal(counter->state_cond);
} }
bool bool
@ -136,21 +134,21 @@ fps_counter_is_started(struct fps_counter *counter) {
void void
fps_counter_interrupt(struct fps_counter *counter) { fps_counter_interrupt(struct fps_counter *counter) {
if (!counter->thread_started) { if (!counter->thread) {
return; return;
} }
sc_mutex_lock(&counter->mutex); mutex_lock(counter->mutex);
counter->interrupted = true; counter->interrupted = true;
sc_mutex_unlock(&counter->mutex); mutex_unlock(counter->mutex);
// wake up blocking wait // wake up blocking wait
sc_cond_signal(&counter->state_cond); cond_signal(counter->state_cond);
} }
void void
fps_counter_join(struct fps_counter *counter) { fps_counter_join(struct fps_counter *counter) {
if (counter->thread_started) { if (counter->thread) {
sc_thread_join(&counter->thread, NULL); SDL_WaitThread(counter->thread, NULL);
} }
} }
@ -160,11 +158,11 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) {
return; return;
} }
sc_mutex_lock(&counter->mutex); mutex_lock(counter->mutex);
uint32_t now = SDL_GetTicks(); uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now); check_interval_expired(counter, now);
++counter->nr_rendered; ++counter->nr_rendered;
sc_mutex_unlock(&counter->mutex); mutex_unlock(counter->mutex);
} }
void void
@ -173,9 +171,9 @@ fps_counter_add_skipped_frame(struct fps_counter *counter) {
return; return;
} }
sc_mutex_lock(&counter->mutex); mutex_lock(counter->mutex);
uint32_t now = SDL_GetTicks(); uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now); check_interval_expired(counter, now);
++counter->nr_skipped; ++counter->nr_skipped;
sc_mutex_unlock(&counter->mutex); mutex_unlock(counter->mutex);
} }

View File

@ -6,15 +6,13 @@
#include <stdatomic.h> #include <stdatomic.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <SDL2/SDL_mutex.h>
#include "util/thread.h" #include <SDL2/SDL_thread.h>
struct fps_counter { struct fps_counter {
sc_thread thread; SDL_Thread *thread;
sc_mutex mutex; SDL_mutex *mutex;
sc_cond state_cond; SDL_cond *state_cond;
bool thread_started;
// atomic so that we can check without locking the mutex // atomic so that we can check without locking the mutex
// if the FPS counter is disabled, we don't want to lock unnecessarily // if the FPS counter is disabled, we don't want to lock unnecessarily

View File

@ -4,6 +4,7 @@
#include <SDL2/SDL_keycode.h> #include <SDL2/SDL_keycode.h>
#include "event_converter.h" #include "event_converter.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
static const int ACTION_DOWN = 1; static const int ACTION_DOWN = 1;
@ -72,10 +73,6 @@ input_manager_init(struct input_manager *im,
im->sdl_shortcut_mods.count = shortcut_mods->count; im->sdl_shortcut_mods.count = shortcut_mods->count;
im->vfinger_down = false; im->vfinger_down = false;
im->last_keycode = SDLK_UNKNOWN;
im->last_mod = 0;
im->key_repeat = 0;
} }
static void static void
@ -150,25 +147,13 @@ action_cut(struct controller *controller, int actions) {
} }
// turn the screen on if it was off, press BACK otherwise // turn the screen on if it was off, press BACK otherwise
// If the screen is off, it is turned on only on ACTION_DOWN
static void static void
press_back_or_turn_screen_on(struct controller *controller, int actions) { press_back_or_turn_screen_on(struct controller *controller) {
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON; msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
if (actions & ACTION_DOWN) {
msg.back_or_screen_on.action = AKEY_EVENT_ACTION_DOWN;
if (!controller_push_msg(controller, &msg)) { if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'press back or turn screen on'"); LOGW("Could not request 'press back or turn screen on'");
return;
}
}
if (actions & ACTION_UP) {
msg.back_or_screen_on.action = AKEY_EVENT_ACTION_UP;
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'press back or turn screen on'");
}
} }
} }
@ -183,19 +168,9 @@ expand_notification_panel(struct controller *controller) {
} }
static void static void
expand_settings_panel(struct controller *controller) { collapse_notification_panel(struct controller *controller) {
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL; msg.type = CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL;
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'expand settings panel'");
}
}
static void
collapse_panels(struct controller *controller) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS;
if (!controller_push_msg(controller, &msg)) { if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'collapse notification panel'"); LOGW("Could not request 'collapse notification panel'");
@ -215,20 +190,13 @@ set_device_clipboard(struct controller *controller, bool paste) {
return; return;
} }
char *text_dup = strdup(text);
SDL_free(text);
if (!text_dup) {
LOGW("Could not strdup input text");
return;
}
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD; msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
msg.set_clipboard.text = text_dup; msg.set_clipboard.text = text;
msg.set_clipboard.paste = paste; msg.set_clipboard.paste = paste;
if (!controller_push_msg(controller, &msg)) { if (!controller_push_msg(controller, &msg)) {
free(text_dup); SDL_free(text);
LOGW("Could not request 'set device clipboard'"); LOGW("Could not request 'set device clipboard'");
} }
} }
@ -274,18 +242,11 @@ clipboard_paste(struct controller *controller) {
return; return;
} }
char *text_dup = strdup(text);
SDL_free(text);
if (!text_dup) {
LOGW("Could not strdup input text");
return;
}
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = text_dup; msg.inject_text.text = text;
if (!controller_push_msg(controller, &msg)) { if (!controller_push_msg(controller, &msg)) {
free(text_dup); SDL_free(text);
LOGW("Could not request 'paste clipboard'"); LOGW("Could not request 'paste clipboard'");
} }
} }
@ -312,7 +273,7 @@ rotate_client_right(struct screen *screen) {
screen_set_rotation(screen, new_rotation); screen_set_rotation(screen, new_rotation);
} }
static void void
input_manager_process_text_input(struct input_manager *im, input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event) { const SDL_TextInputEvent *event) {
if (is_shortcut_mod(im, SDL_GetModState())) { if (is_shortcut_mod(im, SDL_GetModState())) {
@ -330,13 +291,13 @@ input_manager_process_text_input(struct input_manager *im,
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = strdup(event->text); msg.inject_text.text = SDL_strdup(event->text);
if (!msg.inject_text.text) { if (!msg.inject_text.text) {
LOGW("Could not strdup input text"); LOGW("Could not strdup input text");
return; return;
} }
if (!controller_push_msg(im->controller, &msg)) { if (!controller_push_msg(im->controller, &msg)) {
free(msg.inject_text.text); SDL_free(msg.inject_text.text);
LOGW("Could not request 'inject text'"); LOGW("Could not request 'inject text'");
} }
} }
@ -392,33 +353,22 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
return true; return true;
} }
static void void
input_manager_process_key(struct input_manager *im, input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event) { const SDL_KeyboardEvent *event) {
// control: indicates the state of the command-line option --no-control // control: indicates the state of the command-line option --no-control
bool control = im->control; bool control = im->control;
bool smod = is_shortcut_mod(im, event->keysym.mod);
struct controller *controller = im->controller; struct controller *controller = im->controller;
SDL_Keycode keycode = event->keysym.sym; SDL_Keycode keycode = event->keysym.sym;
uint16_t mod = event->keysym.mod;
bool down = event->type == SDL_KEYDOWN; bool down = event->type == SDL_KEYDOWN;
bool ctrl = event->keysym.mod & KMOD_CTRL; bool ctrl = event->keysym.mod & KMOD_CTRL;
bool shift = event->keysym.mod & KMOD_SHIFT; bool shift = event->keysym.mod & KMOD_SHIFT;
bool repeat = event->repeat; bool repeat = event->repeat;
bool smod = is_shortcut_mod(im, mod);
if (down && !repeat) {
if (keycode == im->last_keycode && mod == im->last_mod) {
++im->key_repeat;
} else {
im->key_repeat = 0;
im->last_keycode = keycode;
im->last_mod = mod;
}
}
// The shortcut modifier is pressed // The shortcut modifier is pressed
if (smod) { if (smod) {
int action = down ? ACTION_DOWN : ACTION_UP; int action = down ? ACTION_DOWN : ACTION_UP;
@ -517,17 +467,17 @@ input_manager_process_key(struct input_manager *im,
return; return;
case SDLK_i: case SDLK_i:
if (!shift && !repeat && down) { if (!shift && !repeat && down) {
switch_fps_counter_state(im->fps_counter); struct fps_counter *fps_counter =
im->video_buffer->fps_counter;
switch_fps_counter_state(fps_counter);
} }
return; return;
case SDLK_n: case SDLK_n:
if (control && !repeat && down) { if (control && !repeat && down) {
if (shift) { if (shift) {
collapse_panels(controller); collapse_notification_panel(controller);
} else if (im->key_repeat == 0) {
expand_notification_panel(controller);
} else { } else {
expand_settings_panel(controller); expand_notification_panel(controller);
} }
} }
return; return;
@ -588,7 +538,7 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
return true; return true;
} }
static void void
input_manager_process_mouse_motion(struct input_manager *im, input_manager_process_mouse_motion(struct input_manager *im,
const SDL_MouseMotionEvent *event) { const SDL_MouseMotionEvent *event) {
if (!event->state) { if (!event->state) {
@ -642,7 +592,7 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
return true; return true;
} }
static void void
input_manager_process_touch(struct input_manager *im, input_manager_process_touch(struct input_manager *im,
const SDL_TouchFingerEvent *event) { const SDL_TouchFingerEvent *event) {
struct control_msg msg; struct control_msg msg;
@ -674,7 +624,7 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
return true; return true;
} }
static void void
input_manager_process_mouse_button(struct input_manager *im, input_manager_process_mouse_button(struct input_manager *im,
const SDL_MouseButtonEvent *event) { const SDL_MouseButtonEvent *event) {
bool control = im->control; bool control = im->control;
@ -685,27 +635,13 @@ input_manager_process_mouse_button(struct input_manager *im,
} }
bool down = event->type == SDL_MOUSEBUTTONDOWN; bool down = event->type == SDL_MOUSEBUTTONDOWN;
if (!im->forward_all_clicks) { if (!im->forward_all_clicks && down) {
int action = down ? ACTION_DOWN : ACTION_UP;
if (control && event->button == SDL_BUTTON_X1) {
action_app_switch(im->controller, action);
return;
}
if (control && event->button == SDL_BUTTON_X2 && down) {
if (event->clicks < 2) {
expand_notification_panel(im->controller);
} else {
expand_settings_panel(im->controller);
}
return;
}
if (control && event->button == SDL_BUTTON_RIGHT) { if (control && event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(im->controller, action); press_back_or_turn_screen_on(im->controller);
return; return;
} }
if (control && event->button == SDL_BUTTON_MIDDLE) { if (control && event->button == SDL_BUTTON_MIDDLE) {
action_home(im->controller, action); action_home(im->controller, ACTION_DOWN | ACTION_UP);
return; return;
} }
@ -718,9 +654,7 @@ input_manager_process_mouse_button(struct input_manager *im,
bool outside = x < r->x || x >= r->x + r->w bool outside = x < r->x || x >= r->x + r->w
|| y < r->y || y >= r->y + r->h; || y < r->y || y >= r->y + r->h;
if (outside) { if (outside) {
if (down) {
screen_resize_to_fit(im->screen); screen_resize_to_fit(im->screen);
}
return; return;
} }
} }
@ -789,7 +723,7 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
return true; return true;
} }
static void void
input_manager_process_mouse_wheel(struct input_manager *im, input_manager_process_mouse_wheel(struct input_manager *im,
const SDL_MouseWheelEvent *event) { const SDL_MouseWheelEvent *event) {
struct control_msg msg; struct control_msg msg;
@ -799,46 +733,3 @@ input_manager_process_mouse_wheel(struct input_manager *im,
} }
} }
} }
bool
input_manager_handle_event(struct input_manager *im, SDL_Event *event) {
switch (event->type) {
case SDL_TEXTINPUT:
if (!im->control) {
return true;
}
input_manager_process_text_input(im, &event->text);
return true;
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
// event even if control is disabled
input_manager_process_key(im, &event->key);
return true;
case SDL_MOUSEMOTION:
if (!im->control) {
break;
}
input_manager_process_mouse_motion(im, &event->motion);
return true;
case SDL_MOUSEWHEEL:
if (!im->control) {
break;
}
input_manager_process_mouse_wheel(im, &event->wheel);
return true;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process
// the event even if control is disabled
input_manager_process_mouse_button(im, &event->button);
return true;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
input_manager_process_touch(im, &event->tfinger);
return true;
}
return false;
}

View File

@ -11,10 +11,11 @@
#include "fps_counter.h" #include "fps_counter.h"
#include "scrcpy.h" #include "scrcpy.h"
#include "screen.h" #include "screen.h"
#include "video_buffer.h"
struct input_manager { struct input_manager {
struct controller *controller; struct controller *controller;
struct fps_counter *fps_counter; struct video_buffer *video_buffer;
struct screen *screen; struct screen *screen;
// SDL reports repeated events as a boolean, but Android expects the actual // SDL reports repeated events as a boolean, but Android expects the actual
@ -33,20 +34,34 @@ struct input_manager {
} sdl_shortcut_mods; } sdl_shortcut_mods;
bool vfinger_down; bool vfinger_down;
// Tracks the number of identical consecutive shortcut key down events.
// Not to be confused with event->repeat, which counts the number of
// system-generated repeated key presses.
unsigned key_repeat;
SDL_Keycode last_keycode;
uint16_t last_mod;
}; };
void void
input_manager_init(struct input_manager *im, input_manager_init(struct input_manager *im,
const struct scrcpy_options *options); const struct scrcpy_options *options);
bool void
input_manager_handle_event(struct input_manager *im, SDL_Event *event); input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event);
void
input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event);
void
input_manager_process_mouse_motion(struct input_manager *im,
const SDL_MouseMotionEvent *event);
void
input_manager_process_touch(struct input_manager *im,
const SDL_TouchFingerEvent *event);
void
input_manager_process_mouse_button(struct input_manager *im,
const SDL_MouseButtonEvent *event);
void
input_manager_process_mouse_wheel(struct input_manager *im,
const SDL_MouseWheelEvent *event);
#endif #endif

View File

@ -4,12 +4,12 @@
#include <SDL2/SDL_clipboard.h> #include <SDL2/SDL_clipboard.h>
#include "device_msg.h" #include "device_msg.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
bool bool
receiver_init(struct receiver *receiver, socket_t control_socket) { receiver_init(struct receiver *receiver, socket_t control_socket) {
bool ok = sc_mutex_init(&receiver->mutex); if (!(receiver->mutex = SDL_CreateMutex())) {
if (!ok) {
return false; return false;
} }
receiver->control_socket = control_socket; receiver->control_socket = control_socket;
@ -18,7 +18,7 @@ receiver_init(struct receiver *receiver, socket_t control_socket) {
void void
receiver_destroy(struct receiver *receiver) { receiver_destroy(struct receiver *receiver) {
sc_mutex_destroy(&receiver->mutex); SDL_DestroyMutex(receiver->mutex);
} }
static void static void
@ -101,9 +101,8 @@ bool
receiver_start(struct receiver *receiver) { receiver_start(struct receiver *receiver) {
LOGD("Starting receiver thread"); LOGD("Starting receiver thread");
bool ok = sc_thread_create(&receiver->thread, run_receiver, "receiver", receiver->thread = SDL_CreateThread(run_receiver, "receiver", receiver);
receiver); if (!receiver->thread) {
if (!ok) {
LOGC("Could not start receiver thread"); LOGC("Could not start receiver thread");
return false; return false;
} }
@ -113,5 +112,5 @@ receiver_start(struct receiver *receiver) {
void void
receiver_join(struct receiver *receiver) { receiver_join(struct receiver *receiver) {
sc_thread_join(&receiver->thread, NULL); SDL_WaitThread(receiver->thread, NULL);
} }

View File

@ -4,16 +4,17 @@
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "util/net.h" #include "util/net.h"
#include "util/thread.h"
// receive events from the device // receive events from the device
// managed by the controller // managed by the controller
struct receiver { struct receiver {
socket_t control_socket; socket_t control_socket;
sc_thread thread; SDL_Thread *thread;
sc_mutex mutex; SDL_mutex *mutex;
}; };
bool bool

View File

@ -3,6 +3,7 @@
#include <assert.h> #include <assert.h>
#include <libavutil/time.h> #include <libavutil/time.h>
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
@ -26,7 +27,7 @@ find_muxer(const char *name) {
static struct record_packet * static struct record_packet *
record_packet_new(const AVPacket *packet) { record_packet_new(const AVPacket *packet) {
struct record_packet *rec = malloc(sizeof(*rec)); struct record_packet *rec = SDL_malloc(sizeof(*rec));
if (!rec) { if (!rec) {
return NULL; return NULL;
} }
@ -36,7 +37,7 @@ record_packet_new(const AVPacket *packet) {
av_init_packet(&rec->packet); av_init_packet(&rec->packet);
if (av_packet_ref(&rec->packet, packet)) { if (av_packet_ref(&rec->packet, packet)) {
free(rec); SDL_free(rec);
return NULL; return NULL;
} }
return rec; return rec;
@ -45,7 +46,7 @@ record_packet_new(const AVPacket *packet) {
static void static void
record_packet_delete(struct record_packet *rec) { record_packet_delete(struct record_packet *rec) {
av_packet_unref(&rec->packet); av_packet_unref(&rec->packet);
free(rec); SDL_free(rec);
} }
static void static void
@ -62,24 +63,24 @@ recorder_init(struct recorder *recorder,
const char *filename, const char *filename,
enum sc_record_format format, enum sc_record_format format,
struct size declared_frame_size) { struct size declared_frame_size) {
recorder->filename = strdup(filename); recorder->filename = SDL_strdup(filename);
if (!recorder->filename) { if (!recorder->filename) {
LOGE("Could not strdup filename"); LOGE("Could not strdup filename");
return false; return false;
} }
bool ok = sc_mutex_init(&recorder->mutex); recorder->mutex = SDL_CreateMutex();
if (!ok) { if (!recorder->mutex) {
LOGC("Could not create mutex"); LOGC("Could not create mutex");
free(recorder->filename); SDL_free(recorder->filename);
return false; return false;
} }
ok = sc_cond_init(&recorder->queue_cond); recorder->queue_cond = SDL_CreateCond();
if (!ok) { if (!recorder->queue_cond) {
LOGC("Could not create cond"); LOGC("Could not create cond");
sc_mutex_destroy(&recorder->mutex); SDL_DestroyMutex(recorder->mutex);
free(recorder->filename); SDL_free(recorder->filename);
return false; return false;
} }
@ -96,9 +97,9 @@ recorder_init(struct recorder *recorder,
void void
recorder_destroy(struct recorder *recorder) { recorder_destroy(struct recorder *recorder) {
sc_cond_destroy(&recorder->queue_cond); SDL_DestroyCond(recorder->queue_cond);
sc_mutex_destroy(&recorder->mutex); SDL_DestroyMutex(recorder->mutex);
free(recorder->filename); SDL_free(recorder->filename);
} }
static const char * static const char *
@ -228,7 +229,7 @@ recorder_rescale_packet(struct recorder *recorder, AVPacket *packet) {
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base); av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
} }
static bool bool
recorder_write(struct recorder *recorder, AVPacket *packet) { recorder_write(struct recorder *recorder, AVPacket *packet) {
if (!recorder->header_written) { if (!recorder->header_written) {
if (packet->pts != AV_NOPTS_VALUE) { if (packet->pts != AV_NOPTS_VALUE) {
@ -257,17 +258,17 @@ run_recorder(void *data) {
struct recorder *recorder = data; struct recorder *recorder = data;
for (;;) { for (;;) {
sc_mutex_lock(&recorder->mutex); mutex_lock(recorder->mutex);
while (!recorder->stopped && queue_is_empty(&recorder->queue)) { while (!recorder->stopped && queue_is_empty(&recorder->queue)) {
sc_cond_wait(&recorder->queue_cond, &recorder->mutex); cond_wait(recorder->queue_cond, recorder->mutex);
} }
// if stopped is set, continue to process the remaining events (to // if stopped is set, continue to process the remaining events (to
// finish the recording) before actually stopping // finish the recording) before actually stopping
if (recorder->stopped && queue_is_empty(&recorder->queue)) { if (recorder->stopped && queue_is_empty(&recorder->queue)) {
sc_mutex_unlock(&recorder->mutex); mutex_unlock(recorder->mutex);
struct record_packet *last = recorder->previous; struct record_packet *last = recorder->previous;
if (last) { if (last) {
// assign an arbitrary duration to the last packet // assign an arbitrary duration to the last packet
@ -287,7 +288,7 @@ run_recorder(void *data) {
struct record_packet *rec; struct record_packet *rec;
queue_take(&recorder->queue, next, &rec); queue_take(&recorder->queue, next, &rec);
sc_mutex_unlock(&recorder->mutex); mutex_unlock(recorder->mutex);
// recorder->previous is only written from this thread, no need to lock // recorder->previous is only written from this thread, no need to lock
struct record_packet *previous = recorder->previous; struct record_packet *previous = recorder->previous;
@ -310,11 +311,11 @@ run_recorder(void *data) {
if (!ok) { if (!ok) {
LOGE("Could not record packet"); LOGE("Could not record packet");
sc_mutex_lock(&recorder->mutex); mutex_lock(recorder->mutex);
recorder->failed = true; recorder->failed = true;
// discard pending packets // discard pending packets
recorder_queue_clear(&recorder->queue); recorder_queue_clear(&recorder->queue);
sc_mutex_unlock(&recorder->mutex); mutex_unlock(recorder->mutex);
break; break;
} }
@ -329,9 +330,8 @@ bool
recorder_start(struct recorder *recorder) { recorder_start(struct recorder *recorder) {
LOGD("Starting recorder thread"); LOGD("Starting recorder thread");
bool ok = sc_thread_create(&recorder->thread, run_recorder, "recorder", recorder->thread = SDL_CreateThread(run_recorder, "recorder", recorder);
recorder); if (!recorder->thread) {
if (!ok) {
LOGC("Could not start recorder thread"); LOGC("Could not start recorder thread");
return false; return false;
} }
@ -341,38 +341,38 @@ recorder_start(struct recorder *recorder) {
void void
recorder_stop(struct recorder *recorder) { recorder_stop(struct recorder *recorder) {
sc_mutex_lock(&recorder->mutex); mutex_lock(recorder->mutex);
recorder->stopped = true; recorder->stopped = true;
sc_cond_signal(&recorder->queue_cond); cond_signal(recorder->queue_cond);
sc_mutex_unlock(&recorder->mutex); mutex_unlock(recorder->mutex);
} }
void void
recorder_join(struct recorder *recorder) { recorder_join(struct recorder *recorder) {
sc_thread_join(&recorder->thread, NULL); SDL_WaitThread(recorder->thread, NULL);
} }
bool bool
recorder_push(struct recorder *recorder, const AVPacket *packet) { recorder_push(struct recorder *recorder, const AVPacket *packet) {
sc_mutex_lock(&recorder->mutex); mutex_lock(recorder->mutex);
assert(!recorder->stopped); assert(!recorder->stopped);
if (recorder->failed) { if (recorder->failed) {
// reject any new packet (this will stop the stream) // reject any new packet (this will stop the stream)
sc_mutex_unlock(&recorder->mutex); mutex_unlock(recorder->mutex);
return false; return false;
} }
struct record_packet *rec = record_packet_new(packet); struct record_packet *rec = record_packet_new(packet);
if (!rec) { if (!rec) {
LOGC("Could not allocate record packet"); LOGC("Could not allocate record packet");
sc_mutex_unlock(&recorder->mutex); mutex_unlock(recorder->mutex);
return false; return false;
} }
queue_push(&recorder->queue, next, rec); queue_push(&recorder->queue, next, rec);
sc_cond_signal(&recorder->queue_cond); cond_signal(recorder->queue_cond);
sc_mutex_unlock(&recorder->mutex); mutex_unlock(recorder->mutex);
return true; return true;
} }

View File

@ -5,11 +5,12 @@
#include <stdbool.h> #include <stdbool.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "coords.h" #include "coords.h"
#include "scrcpy.h" #include "scrcpy.h"
#include "util/queue.h" #include "util/queue.h"
#include "util/thread.h"
struct record_packet { struct record_packet {
AVPacket packet; AVPacket packet;
@ -25,9 +26,9 @@ struct recorder {
struct size declared_frame_size; struct size declared_frame_size;
bool header_written; bool header_written;
sc_thread thread; SDL_Thread *thread;
sc_mutex mutex; SDL_mutex *mutex;
sc_cond queue_cond; SDL_cond *queue_cond;
bool stopped; // set on recorder_stop() by the stream reader bool stopped; // set on recorder_stop() by the stream reader
bool failed; // set on packet write failure bool failed; // set on packet write failure
struct recorder_queue queue; struct recorder_queue queue;

View File

@ -26,11 +26,12 @@
#include "stream.h" #include "stream.h"
#include "tiny_xpm.h" #include "tiny_xpm.h"
#include "video_buffer.h" #include "video_buffer.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
static struct server server; static struct server server;
static struct screen screen; static struct screen screen = SCREEN_INITIALIZER;
static struct fps_counter fps_counter; static struct fps_counter fps_counter;
static struct video_buffer video_buffer; static struct video_buffer video_buffer;
static struct stream stream; static struct stream stream;
@ -41,7 +42,7 @@ static struct file_handler file_handler;
static struct input_manager input_manager = { static struct input_manager input_manager = {
.controller = &controller, .controller = &controller,
.fps_counter = &fps_counter, .video_buffer = &video_buffer,
.screen = &screen, .screen = &screen,
.repeat = 0, .repeat = 0,
@ -128,6 +129,30 @@ sdl_init_and_configure(bool display, const char *render_driver,
return true; return true;
} }
#if defined(__APPLE__) || defined(__WINDOWS__)
# define CONTINUOUS_RESIZING_WORKAROUND
#endif
#ifdef CONTINUOUS_RESIZING_WORKAROUND
// On Windows and MacOS, resizing blocks the event loop, so resizing events are
// not triggered. As a workaround, handle them in an event handler.
//
// <https://bugzilla.libsdl.org/show_bug.cgi?id=2077>
// <https://stackoverflow.com/a/40693139/1987178>
static int
event_watcher(void *data, SDL_Event *event) {
(void) data;
if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
// In practice, it seems to always be called from the same thread in
// that specific case. Anyway, it's just a workaround.
screen_render(&screen, true);
}
return 0;
}
#endif
static bool static bool
is_apk(const char *file) { is_apk(const char *file) {
const char *ext = strrchr(file, '.'); const char *ext = strrchr(file, '.');
@ -149,42 +174,78 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) {
case SDL_QUIT: case SDL_QUIT:
LOGD("User requested to quit"); LOGD("User requested to quit");
return EVENT_RESULT_STOPPED_BY_USER; return EVENT_RESULT_STOPPED_BY_USER;
case EVENT_NEW_FRAME:
if (!screen.has_frame) {
screen.has_frame = true;
// this is the very first frame, show the window
screen_show_window(&screen);
}
if (!screen_update_frame(&screen, &video_buffer)) {
return EVENT_RESULT_CONTINUE;
}
break;
case SDL_WINDOWEVENT:
screen_handle_window_event(&screen, &event->window);
break;
case SDL_TEXTINPUT:
if (!options->control) {
break;
}
input_manager_process_text_input(&input_manager, &event->text);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
// event even if control is disabled
input_manager_process_key(&input_manager, &event->key);
break;
case SDL_MOUSEMOTION:
if (!options->control) {
break;
}
input_manager_process_mouse_motion(&input_manager, &event->motion);
break;
case SDL_MOUSEWHEEL:
if (!options->control) {
break;
}
input_manager_process_mouse_wheel(&input_manager, &event->wheel);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process
// the event even if control is disabled
input_manager_process_mouse_button(&input_manager, &event->button);
break;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
input_manager_process_touch(&input_manager, &event->tfinger);
break;
case SDL_DROPFILE: { case SDL_DROPFILE: {
if (!options->control) { if (!options->control) {
break; break;
} }
char *file = strdup(event->drop.file);
SDL_free(event->drop.file);
if (!file) {
LOGW("Could not strdup drop filename\n");
break;
}
file_handler_action_t action; file_handler_action_t action;
if (is_apk(file)) { if (is_apk(event->drop.file)) {
action = ACTION_INSTALL_APK; action = ACTION_INSTALL_APK;
} else { } else {
action = ACTION_PUSH_FILE; action = ACTION_PUSH_FILE;
} }
file_handler_request(&file_handler, action, file); file_handler_request(&file_handler, action, event->drop.file);
goto end; break;
} }
} }
bool consumed = screen_handle_event(&screen, event);
if (consumed) {
goto end;
}
consumed = input_manager_handle_event(&input_manager, event);
(void) consumed;
end:
return EVENT_RESULT_CONTINUE; return EVENT_RESULT_CONTINUE;
} }
static bool static bool
event_loop(const struct scrcpy_options *options) { event_loop(const struct scrcpy_options *options) {
#ifdef CONTINUOUS_RESIZING_WORKAROUND
if (options->display) {
SDL_AddEventWatch(event_watcher, NULL);
}
#endif
SDL_Event event; SDL_Event event;
while (SDL_WaitEvent(&event)) { while (SDL_WaitEvent(&event)) {
enum event_result result = handle_event(&event, options); enum event_result result = handle_event(&event, options);
@ -225,7 +286,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
if (priority == 0) { if (priority == 0) {
return; return;
} }
char *local_fmt = malloc(strlen(fmt) + 10); char *local_fmt = SDL_malloc(strlen(fmt) + 10);
if (!local_fmt) { if (!local_fmt) {
LOGC("Could not allocate string"); LOGC("Could not allocate string");
return; return;
@ -234,7 +295,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
strcpy(local_fmt, "[FFmpeg] "); strcpy(local_fmt, "[FFmpeg] ");
strcpy(local_fmt + 9, fmt); strcpy(local_fmt + 9, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl); SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
free(local_fmt); SDL_free(local_fmt);
} }
bool bool
@ -253,7 +314,6 @@ scrcpy(const struct scrcpy_options *options) {
bool stream_started = false; bool stream_started = false;
bool controller_initialized = false; bool controller_initialized = false;
bool controller_started = false; bool controller_started = false;
bool screen_initialized = false;
bool record = !!options->record_filename; bool record = !!options->record_filename;
struct server_params params = { struct server_params params = {
@ -271,7 +331,6 @@ scrcpy(const struct scrcpy_options *options) {
.codec_options = options->codec_options, .codec_options = options->codec_options,
.encoder_name = options->encoder_name, .encoder_name = options->encoder_name,
.force_adb_forward = options->force_adb_forward, .force_adb_forward = options->force_adb_forward,
.power_off_on_close = options->power_off_on_close,
}; };
if (!server_start(&server, options->serial, &params)) { if (!server_start(&server, options->serial, &params)) {
goto end; goto end;
@ -305,7 +364,8 @@ scrcpy(const struct scrcpy_options *options) {
} }
fps_counter_initialized = true; fps_counter_initialized = true;
if (!video_buffer_init(&video_buffer, options->render_expired_frames)) { if (!video_buffer_init(&video_buffer, &fps_counter,
options->render_expired_frames)) {
goto end; goto end;
} }
video_buffer_initialized = true; video_buffer_initialized = true;
@ -338,6 +398,13 @@ scrcpy(const struct scrcpy_options *options) {
stream_init(&stream, server.video_socket, dec, rec); stream_init(&stream, server.video_socket, dec, rec);
// now we consumed the header values, the socket receives the video stream
// start the stream
if (!stream_start(&stream)) {
goto end;
}
stream_started = true;
if (options->display) { if (options->display) {
if (options->control) { if (options->control) {
if (!controller_init(&controller, server.control_socket)) { if (!controller_init(&controller, server.control_socket)) {
@ -354,25 +421,14 @@ scrcpy(const struct scrcpy_options *options) {
const char *window_title = const char *window_title =
options->window_title ? options->window_title : device_name; options->window_title ? options->window_title : device_name;
struct screen_params screen_params = { if (!screen_init_rendering(&screen, window_title, frame_size,
.window_title = window_title, options->always_on_top, options->window_x,
.frame_size = frame_size, options->window_y, options->window_width,
.always_on_top = options->always_on_top, options->window_height,
.window_x = options->window_x, options->window_borderless,
.window_y = options->window_y, options->rotation, options->mipmaps)) {
.window_width = options->window_width,
.window_height = options->window_height,
.window_borderless = options->window_borderless,
.rotation = options->rotation,
.mipmaps = options->mipmaps,
.fullscreen = options->fullscreen,
};
if (!screen_init(&screen, &video_buffer, &fps_counter,
&screen_params)) {
goto end; goto end;
} }
screen_initialized = true;
if (options->turn_screen_off) { if (options->turn_screen_off) {
struct control_msg msg; struct control_msg msg;
@ -383,20 +439,19 @@ scrcpy(const struct scrcpy_options *options) {
LOGW("Could not request 'set screen power mode'"); LOGW("Could not request 'set screen power mode'");
} }
} }
}
// now we consumed the header values, the socket receives the video stream if (options->fullscreen) {
// start the stream screen_switch_fullscreen(&screen);
if (!stream_start(&stream)) { }
goto end;
} }
stream_started = true;
input_manager_init(&input_manager, options); input_manager_init(&input_manager, options);
ret = event_loop(options); ret = event_loop(options);
LOGD("quit..."); LOGD("quit...");
screen_destroy(&screen);
end: end:
// stop stream and controller so that they don't continue once their socket // stop stream and controller so that they don't continue once their socket
// is shutdown // is shutdown
@ -423,13 +478,6 @@ end:
if (stream_started) { if (stream_started) {
stream_join(&stream); stream_join(&stream);
} }
// 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) {
screen_destroy(&screen);
}
if (controller_started) { if (controller_started) {
controller_join(&controller); controller_join(&controller);
} }

View File

@ -82,7 +82,6 @@ struct scrcpy_options {
bool forward_key_repeat; bool forward_key_repeat;
bool forward_all_clicks; bool forward_all_clicks;
bool legacy_paste; bool legacy_paste;
bool power_off_on_close;
}; };
#define SCRCPY_OPTIONS_DEFAULT { \ #define SCRCPY_OPTIONS_DEFAULT { \
@ -104,10 +103,10 @@ struct scrcpy_options {
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \ .data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
.count = 2, \ .count = 2, \
}, \ }, \
.max_size = 0, \ .max_size = DEFAULT_MAX_SIZE, \
.bit_rate = DEFAULT_BIT_RATE, \ .bit_rate = DEFAULT_BIT_RATE, \
.max_fps = 0, \ .max_fps = 0, \
.lock_video_orientation = -1, \ .lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \
.rotation = 0, \ .rotation = 0, \
.window_x = SC_WINDOW_POSITION_UNDEFINED, \ .window_x = SC_WINDOW_POSITION_UNDEFINED, \
.window_y = SC_WINDOW_POSITION_UNDEFINED, \ .window_y = SC_WINDOW_POSITION_UNDEFINED, \
@ -130,7 +129,6 @@ struct scrcpy_options {
.forward_key_repeat = true, \ .forward_key_repeat = true, \
.forward_all_clicks = false, \ .forward_all_clicks = false, \
.legacy_paste = false, \ .legacy_paste = false, \
.power_off_on_close = false, \
} }
bool bool

View File

@ -4,11 +4,11 @@
#include <string.h> #include <string.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "events.h"
#include "icon.xpm" #include "icon.xpm"
#include "scrcpy.h" #include "scrcpy.h"
#include "tiny_xpm.h" #include "tiny_xpm.h"
#include "video_buffer.h" #include "video_buffer.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#define DISPLAY_MARGINS 96 #define DISPLAY_MARGINS 96
@ -191,25 +191,9 @@ screen_update_content_rect(struct screen *screen) {
} }
} }
static void void
on_frame_available(struct video_buffer *vb, void *userdata) { screen_init(struct screen *screen) {
(void) vb; *screen = (struct screen) SCREEN_INITIALIZER;
(void) userdata;
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
// Post the event on the UI thread
SDL_PushEvent(&new_frame_event);
}
static void
on_frame_skipped(struct video_buffer *vb, void *userdata) {
(void) vb;
struct screen *screen = userdata;
fps_counter_add_skipped_frame(screen->fps_counter);
} }
static inline SDL_Texture * static inline SDL_Texture *
@ -239,63 +223,27 @@ create_texture(struct screen *screen) {
return texture; return texture;
} }
#if defined(__APPLE__) || defined(__WINDOWS__)
# define CONTINUOUS_RESIZING_WORKAROUND
#endif
#ifdef CONTINUOUS_RESIZING_WORKAROUND
// On Windows and MacOS, resizing blocks the event loop, so resizing events are
// not triggered. As a workaround, handle them in an event handler.
//
// <https://bugzilla.libsdl.org/show_bug.cgi?id=2077>
// <https://stackoverflow.com/a/40693139/1987178>
static int
event_watcher(void *data, SDL_Event *event) {
struct screen *screen = data;
if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
// In practice, it seems to always be called from the same thread in
// that specific case. Anyway, it's just a workaround.
screen_render(screen, true);
}
return 0;
}
#endif
bool bool
screen_init(struct screen *screen, struct video_buffer *vb, screen_init_rendering(struct screen *screen, const char *window_title,
struct fps_counter *fps_counter, struct size frame_size, bool always_on_top,
const struct screen_params *params) { int16_t window_x, int16_t window_y, uint16_t window_width,
screen->vb = vb; uint16_t window_height, bool window_borderless,
screen->fps_counter = fps_counter; uint8_t rotation, bool mipmaps) {
screen->frame_size = frame_size;
screen->resize_pending = false; screen->rotation = rotation;
screen->has_frame = false; if (rotation) {
screen->fullscreen = false; LOGI("Initial display rotation set to %u", rotation);
screen->maximized = false;
static const struct video_buffer_callbacks cbs = {
.on_frame_available = on_frame_available,
.on_frame_skipped = on_frame_skipped,
};
video_buffer_set_consumer_callbacks(vb, &cbs, screen);
screen->frame_size = params->frame_size;
screen->rotation = params->rotation;
if (screen->rotation) {
LOGI("Initial display rotation set to %u", screen->rotation);
} }
struct size content_size = struct size content_size = get_rotated_size(frame_size, screen->rotation);
get_rotated_size(screen->frame_size, screen->rotation);
screen->content_size = content_size; screen->content_size = content_size;
struct size window_size = get_initial_optimal_size(content_size, struct size window_size =
params->window_width, get_initial_optimal_size(content_size, window_width, window_height);
params->window_height); uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
uint32_t window_flags = SDL_WINDOW_HIDDEN #ifdef HIDPI_SUPPORT
| SDL_WINDOW_RESIZABLE window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
| SDL_WINDOW_ALLOW_HIGHDPI; #endif
if (params->always_on_top) { if (always_on_top) {
#ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP #ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP; window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
#else #else
@ -303,15 +251,15 @@ screen_init(struct screen *screen, struct video_buffer *vb,
"(compile with SDL >= 2.0.5 to enable it)"); "(compile with SDL >= 2.0.5 to enable it)");
#endif #endif
} }
if (params->window_borderless) { if (window_borderless) {
window_flags |= SDL_WINDOW_BORDERLESS; window_flags |= SDL_WINDOW_BORDERLESS;
} }
int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED int x = window_x != SC_WINDOW_POSITION_UNDEFINED
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED; ? window_x : (int) SDL_WINDOWPOS_UNDEFINED;
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED int y = window_y != SC_WINDOW_POSITION_UNDEFINED
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED; ? window_y : (int) SDL_WINDOWPOS_UNDEFINED;
screen->window = SDL_CreateWindow(params->window_title, x, y, screen->window = SDL_CreateWindow(window_title, x, y,
window_size.width, window_size.height, window_size.width, window_size.height,
window_flags); window_flags);
if (!screen->window) { if (!screen->window) {
@ -323,7 +271,7 @@ screen_init(struct screen *screen, struct video_buffer *vb,
SDL_RENDERER_ACCELERATED); SDL_RENDERER_ACCELERATED);
if (!screen->renderer) { if (!screen->renderer) {
LOGC("Could not create renderer: %s", SDL_GetError()); LOGC("Could not create renderer: %s", SDL_GetError());
SDL_DestroyWindow(screen->window); screen_destroy(screen);
return false; return false;
} }
@ -332,17 +280,15 @@ screen_init(struct screen *screen, struct video_buffer *vb,
const char *renderer_name = r ? NULL : renderer_info.name; const char *renderer_name = r ? NULL : renderer_info.name;
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)"); LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
screen->mipmaps = false;
// starts with "opengl" // starts with "opengl"
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6); screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
if (use_opengl) { if (screen->use_opengl) {
struct sc_opengl *gl = &screen->gl; struct sc_opengl *gl = &screen->gl;
sc_opengl_init(gl); sc_opengl_init(gl);
LOGI("OpenGL version: %s", gl->version); LOGI("OpenGL version: %s", gl->version);
if (params->mipmaps) { if (mipmaps) {
bool supports_mipmaps = bool supports_mipmaps =
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */ sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
2, 0 /* OpenGL ES 2.0+ */); 2, 0 /* OpenGL ES 2.0+ */);
@ -356,7 +302,7 @@ screen_init(struct screen *screen, struct video_buffer *vb,
} else { } else {
LOGI("Trilinear filtering disabled"); LOGI("Trilinear filtering disabled");
} }
} else if (params->mipmaps) { } else {
LOGD("Trilinear filtering disabled (not an OpenGL renderer)"); LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
} }
@ -368,13 +314,12 @@ screen_init(struct screen *screen, struct video_buffer *vb,
LOGW("Could not load icon"); LOGW("Could not load icon");
} }
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, params->frame_size.width, LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width,
params->frame_size.height); frame_size.height);
screen->texture = create_texture(screen); screen->texture = create_texture(screen);
if (!screen->texture) { if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError()); LOGC("Could not create texture: %s", SDL_GetError());
SDL_DestroyRenderer(screen->renderer); screen_destroy(screen);
SDL_DestroyWindow(screen->window);
return false; return false;
} }
@ -385,28 +330,26 @@ screen_init(struct screen *screen, struct video_buffer *vb,
screen_update_content_rect(screen); screen_update_content_rect(screen);
if (params->fullscreen) {
screen_switch_fullscreen(screen);
}
#ifdef CONTINUOUS_RESIZING_WORKAROUND
SDL_AddEventWatch(event_watcher, screen);
#endif
return true; return true;
} }
static void void
screen_show_window(struct screen *screen) { screen_show_window(struct screen *screen) {
SDL_ShowWindow(screen->window); SDL_ShowWindow(screen->window);
} }
void void
screen_destroy(struct screen *screen) { screen_destroy(struct screen *screen) {
if (screen->texture) {
SDL_DestroyTexture(screen->texture); SDL_DestroyTexture(screen->texture);
}
if (screen->renderer) {
SDL_DestroyRenderer(screen->renderer); SDL_DestroyRenderer(screen->renderer);
}
if (screen->window) {
SDL_DestroyWindow(screen->window); SDL_DestroyWindow(screen->window);
} }
}
static void static void
resize_for_content(struct screen *screen, struct size old_content_size, resize_for_content(struct screen *screen, struct size old_content_size,
@ -502,23 +445,24 @@ update_texture(struct screen *screen, const AVFrame *frame) {
frame->data[2], frame->linesize[2]); frame->data[2], frame->linesize[2]);
if (screen->mipmaps) { if (screen->mipmaps) {
assert(screen->use_opengl);
SDL_GL_BindTexture(screen->texture, NULL, NULL); SDL_GL_BindTexture(screen->texture, NULL, NULL);
screen->gl.GenerateMipmap(GL_TEXTURE_2D); screen->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(screen->texture); SDL_GL_UnbindTexture(screen->texture);
} }
} }
static bool bool
screen_update_frame(struct screen *screen) { screen_update_frame(struct screen *screen, struct video_buffer *vb) {
const AVFrame *frame = video_buffer_consumer_take_frame(screen->vb); mutex_lock(vb->mutex);
const AVFrame *frame = video_buffer_consume_rendered_frame(vb);
fps_counter_add_rendered_frame(screen->fps_counter);
struct size new_frame_size = {frame->width, frame->height}; struct size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) { if (!prepare_for_frame(screen, new_frame_size)) {
mutex_unlock(vb->mutex);
return false; return false;
} }
update_texture(screen, frame); update_texture(screen, frame);
mutex_unlock(vb->mutex);
screen_render(screen, false); screen_render(screen, false);
return true; return true;
@ -605,26 +549,10 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
content_size.height); content_size.height);
} }
bool void
screen_handle_event(struct screen *screen, SDL_Event *event) { screen_handle_window_event(struct screen *screen,
switch (event->type) { const SDL_WindowEvent *event) {
case EVENT_NEW_FRAME: switch (event->event) {
if (!screen->has_frame) {
screen->has_frame = true;
// this is the very first frame, show the window
screen_show_window(screen);
}
bool ok = screen_update_frame(screen);
if (!ok) {
LOGW("Frame update failed\n");
}
return true;
case SDL_WINDOWEVENT:
if (!screen->has_frame) {
// Do nothing
return true;
}
switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED: case SDL_WINDOWEVENT_EXPOSED:
screen_render(screen, true); screen_render(screen, true);
break; break;
@ -636,21 +564,16 @@ screen_handle_event(struct screen *screen, SDL_Event *event) {
break; break;
case SDL_WINDOWEVENT_RESTORED: case SDL_WINDOWEVENT_RESTORED:
if (screen->fullscreen) { if (screen->fullscreen) {
// On Windows, in maximized+fullscreen, disabling // On Windows, in maximized+fullscreen, disabling fullscreen
// fullscreen mode unexpectedly triggers the "restored" // mode unexpectedly triggers the "restored" then "maximized"
// then "maximized" events, leaving the window in a // events, leaving the window in a weird state (maximized
// weird state (maximized according to the events, but // according to the events, but not maximized visually).
// not maximized visually).
break; break;
} }
screen->maximized = false; screen->maximized = false;
apply_pending_resize(screen); apply_pending_resize(screen);
break; break;
} }
return true;
}
return false;
} }
struct point struct point

View File

@ -13,12 +13,10 @@
struct video_buffer; struct video_buffer;
struct screen { struct screen {
struct video_buffer *vb;
struct fps_counter *fps_counter;
SDL_Window *window; SDL_Window *window;
SDL_Renderer *renderer; SDL_Renderer *renderer;
SDL_Texture *texture; SDL_Texture *texture;
bool use_opengl;
struct sc_opengl gl; struct sc_opengl gl;
struct size frame_size; struct size frame_size;
struct size content_size; // rotated frame_size struct size content_size; // rotated frame_size
@ -35,37 +33,68 @@ struct screen {
bool has_frame; bool has_frame;
bool fullscreen; bool fullscreen;
bool maximized; bool maximized;
bool no_window;
bool mipmaps; bool mipmaps;
}; };
struct screen_params { #define SCREEN_INITIALIZER { \
const char *window_title; .window = NULL, \
struct size frame_size; .renderer = NULL, \
bool always_on_top; .texture = NULL, \
.use_opengl = false, \
.gl = {0}, \
.frame_size = { \
.width = 0, \
.height = 0, \
}, \
.content_size = { \
.width = 0, \
.height = 0, \
}, \
.resize_pending = false, \
.windowed_content_size = { \
.width = 0, \
.height = 0, \
}, \
.rotation = 0, \
.rect = { \
.x = 0, \
.y = 0, \
.w = 0, \
.h = 0, \
}, \
.has_frame = false, \
.fullscreen = false, \
.maximized = false, \
.no_window = false, \
.mipmaps = false, \
}
int16_t window_x; // initialize default values
int16_t window_y; void
uint16_t window_width; // accepts SC_WINDOW_POSITION_UNDEFINED screen_init(struct screen *screen);
uint16_t window_height; // accepts SC_WINDOW_POSITION_UNDEFINED
bool window_borderless;
uint8_t rotation;
bool mipmaps;
bool fullscreen;
};
// initialize screen, create window, renderer and texture (window is hidden) // initialize screen, create window, renderer and texture (window is hidden)
// window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED
bool bool
screen_init(struct screen *screen, struct video_buffer *vb, screen_init_rendering(struct screen *screen, const char *window_title,
struct fps_counter *fps_counter, struct size frame_size, bool always_on_top,
const struct screen_params *params); int16_t window_x, int16_t window_y, uint16_t window_width,
uint16_t window_height, bool window_borderless,
uint8_t rotation, bool mipmaps);
// show the window
void
screen_show_window(struct screen *screen);
// destroy window, renderer and texture (if any) // destroy window, renderer and texture (if any)
void void
screen_destroy(struct screen *screen); screen_destroy(struct screen *screen);
// resize if necessary and write the rendered frame into the texture
bool
screen_update_frame(struct screen *screen, struct video_buffer *vb);
// render the texture to the renderer // render the texture to the renderer
// //
// Set the update_content_rect flag if the window or content size may have // Set the update_content_rect flag if the window or content size may have
@ -89,9 +118,9 @@ screen_resize_to_pixel_perfect(struct screen *screen);
void void
screen_set_rotation(struct screen *screen, unsigned rotation); screen_set_rotation(struct screen *screen, unsigned rotation);
// react to SDL events // react to window events
bool void
screen_handle_event(struct screen *screen, SDL_Event *event); screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
// convert point from window coordinates to frame coordinates // convert point from window coordinates to frame coordinates
// x and y are expressed in pixels // x and y are expressed in pixels

View File

@ -5,10 +5,12 @@
#include <inttypes.h> #include <inttypes.h>
#include <libgen.h> #include <libgen.h>
#include <stdio.h> #include <stdio.h>
#include <SDL2/SDL_thread.h>
#include <SDL2/SDL_timer.h> #include <SDL2/SDL_timer.h>
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
#include "adb.h" #include "adb.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
#include "util/str_util.h" #include "util/str_util.h"
@ -31,7 +33,7 @@ get_server_path(void) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
char *server_path = utf8_from_wide_char(server_path_env); char *server_path = utf8_from_wide_char(server_path_env);
#else #else
char *server_path = strdup(server_path_env); char *server_path = SDL_strdup(server_path_env);
#endif #endif
if (!server_path) { if (!server_path) {
LOGE("Could not allocate memory"); LOGE("Could not allocate memory");
@ -43,7 +45,7 @@ get_server_path(void) {
#ifndef PORTABLE #ifndef PORTABLE
LOGD("Using server: " DEFAULT_SERVER_PATH); LOGD("Using server: " DEFAULT_SERVER_PATH);
char *server_path = strdup(DEFAULT_SERVER_PATH); char *server_path = SDL_strdup(DEFAULT_SERVER_PATH);
if (!server_path) { if (!server_path) {
LOGE("Could not allocate memory"); LOGE("Could not allocate memory");
return NULL; return NULL;
@ -65,11 +67,11 @@ get_server_path(void) {
// sizeof(SERVER_FILENAME) gives statically the size including the null byte // sizeof(SERVER_FILENAME) gives statically the size including the null byte
size_t len = dirlen + 1 + sizeof(SERVER_FILENAME); size_t len = dirlen + 1 + sizeof(SERVER_FILENAME);
char *server_path = malloc(len); char *server_path = SDL_malloc(len);
if (!server_path) { if (!server_path) {
LOGE("Could not alloc server path string, " LOGE("Could not alloc server path string, "
"using " SERVER_FILENAME " from current directory"); "using " SERVER_FILENAME " from current directory");
free(executable_path); SDL_free(executable_path);
return SERVER_FILENAME; return SERVER_FILENAME;
} }
@ -78,7 +80,7 @@ get_server_path(void) {
memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME)); memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME));
// the final null byte has been copied with SERVER_FILENAME // the final null byte has been copied with SERVER_FILENAME
free(executable_path); SDL_free(executable_path);
LOGD("Using server (portable): %s", server_path); LOGD("Using server (portable): %s", server_path);
return server_path; return server_path;
@ -93,36 +95,36 @@ push_server(const char *serial) {
} }
if (!is_regular_file(server_path)) { if (!is_regular_file(server_path)) {
LOGE("'%s' does not exist or is not a regular file\n", server_path); LOGE("'%s' does not exist or is not a regular file\n", server_path);
free(server_path); SDL_free(server_path);
return false; return false;
} }
process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH); process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH);
free(server_path); SDL_free(server_path);
return process_check_success(process, "adb push", true); return process_check_success(process, "adb push");
} }
static bool static bool
enable_tunnel_reverse(const char *serial, uint16_t local_port) { enable_tunnel_reverse(const char *serial, uint16_t local_port) {
process_t process = adb_reverse(serial, SOCKET_NAME, local_port); process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
return process_check_success(process, "adb reverse", true); return process_check_success(process, "adb reverse");
} }
static bool static bool
disable_tunnel_reverse(const char *serial) { disable_tunnel_reverse(const char *serial) {
process_t process = adb_reverse_remove(serial, SOCKET_NAME); process_t process = adb_reverse_remove(serial, SOCKET_NAME);
return process_check_success(process, "adb reverse --remove", true); return process_check_success(process, "adb reverse --remove");
} }
static bool static bool
enable_tunnel_forward(const char *serial, uint16_t local_port) { enable_tunnel_forward(const char *serial, uint16_t local_port) {
process_t process = adb_forward(serial, local_port, SOCKET_NAME); process_t process = adb_forward(serial, local_port, SOCKET_NAME);
return process_check_success(process, "adb forward", true); return process_check_success(process, "adb forward");
} }
static bool static bool
disable_tunnel_forward(const char *serial, uint16_t local_port) { disable_tunnel_forward(const char *serial, uint16_t local_port) {
process_t process = adb_forward_remove(serial, local_port); process_t process = adb_forward_remove(serial, local_port);
return process_check_success(process, "adb forward --remove", true); return process_check_success(process, "adb forward --remove");
} }
static bool static bool
@ -293,7 +295,6 @@ execute_server(struct server *server, const struct server_params *params) {
params->stay_awake ? "true" : "false", params->stay_awake ? "true" : "false",
params->codec_options ? params->codec_options : "-", params->codec_options ? params->codec_options : "-",
params->encoder_name ? params->encoder_name : "-", params->encoder_name ? params->encoder_name : "-",
params->power_off_on_close ? "true" : "false",
}; };
#ifdef SERVER_DEBUGGER #ifdef SERVER_DEBUGGER
LOGI("Server debugger waiting for a client on device port " LOGI("Server debugger waiting for a client on device port "
@ -356,17 +357,18 @@ bool
server_init(struct server *server) { server_init(struct server *server) {
server->serial = NULL; server->serial = NULL;
server->process = PROCESS_NONE; server->process = PROCESS_NONE;
server->wait_server_thread = NULL;
atomic_flag_clear_explicit(&server->server_socket_closed, atomic_flag_clear_explicit(&server->server_socket_closed,
memory_order_relaxed); memory_order_relaxed);
bool ok = sc_mutex_init(&server->mutex); server->mutex = SDL_CreateMutex();
if (!ok) { if (!server->mutex) {
return false; return false;
} }
ok = sc_cond_init(&server->process_terminated_cond); server->process_terminated_cond = SDL_CreateCond();
if (!ok) { if (!server->process_terminated_cond) {
sc_mutex_destroy(&server->mutex); SDL_DestroyMutex(server->mutex);
return false; return false;
} }
@ -376,6 +378,8 @@ server_init(struct server *server) {
server->video_socket = INVALID_SOCKET; server->video_socket = INVALID_SOCKET;
server->control_socket = INVALID_SOCKET; server->control_socket = INVALID_SOCKET;
server->port_range.first = 0;
server->port_range.last = 0;
server->local_port = 0; server->local_port = 0;
server->tunnel_enabled = false; server->tunnel_enabled = false;
@ -387,12 +391,12 @@ server_init(struct server *server) {
static int static int
run_wait_server(void *data) { run_wait_server(void *data) {
struct server *server = data; struct server *server = data;
process_wait(server->process, false); // ignore exit code process_wait_noclose(server->process, NULL); // ignore exit code
sc_mutex_lock(&server->mutex); mutex_lock(server->mutex);
server->process_terminated = true; server->process_terminated = true;
sc_cond_signal(&server->process_terminated_cond); cond_signal(server->process_terminated_cond);
sc_mutex_unlock(&server->mutex); mutex_unlock(server->mutex);
// no need for synchronization, server_socket is initialized before this // no need for synchronization, server_socket is initialized before this
// thread was created // thread was created
@ -409,8 +413,10 @@ run_wait_server(void *data) {
bool bool
server_start(struct server *server, const char *serial, server_start(struct server *server, const char *serial,
const struct server_params *params) { const struct server_params *params) {
server->port_range = params->port_range;
if (serial) { if (serial) {
server->serial = strdup(serial); server->serial = SDL_strdup(serial);
if (!server->serial) { if (!server->serial) {
return false; return false;
} }
@ -437,11 +443,11 @@ server_start(struct server *server, const char *serial,
// things simple and multiplatform, just spawn a new thread waiting for the // things simple and multiplatform, just spawn a new thread waiting for the
// server process and calling shutdown()/close() on the server socket if // server process and calling shutdown()/close() on the server socket if
// necessary to wake up any accept() blocking call. // necessary to wake up any accept() blocking call.
bool ok = sc_thread_create(&server->wait_server_thread, run_wait_server, server->wait_server_thread =
"wait-server", server); SDL_CreateThread(run_wait_server, "wait-server", server);
if (!ok) { if (!server->wait_server_thread) {
process_terminate(server->process); process_terminate(server->process);
process_wait(server->process, true); // ignore exit code process_wait(server->process, NULL); // ignore exit code
goto error2; goto error2;
} }
@ -460,7 +466,7 @@ error2:
} }
disable_tunnel(server); disable_tunnel(server);
error1: error1:
free(server->serial); SDL_free(server->serial);
return false; return false;
} }
@ -529,33 +535,33 @@ server_stop(struct server *server) {
} }
// Give some delay for the server to terminate properly // Give some delay for the server to terminate properly
sc_mutex_lock(&server->mutex); mutex_lock(server->mutex);
bool signaled = false; int r = 0;
if (!server->process_terminated) { if (!server->process_terminated) {
#define WATCHDOG_DELAY_MS 1000 #define WATCHDOG_DELAY_MS 1000
signaled = sc_cond_timedwait(&server->process_terminated_cond, r = cond_wait_timeout(server->process_terminated_cond,
&server->mutex, server->mutex,
WATCHDOG_DELAY_MS); WATCHDOG_DELAY_MS);
} }
sc_mutex_unlock(&server->mutex); mutex_unlock(server->mutex);
// After this delay, kill the server if it's not dead already. // After this delay, kill the server if it's not dead already.
// On some devices, closing the sockets is not sufficient to wake up the // On some devices, closing the sockets is not sufficient to wake up the
// blocking calls while the device is asleep. // blocking calls while the device is asleep.
if (!signaled) { if (r == SDL_MUTEX_TIMEDOUT) {
// The process is terminated, but not reaped (closed) yet, so its PID // The process is terminated, but not reaped (closed) yet, so its PID
// is still valid. // is still valid.
LOGW("Killing the server..."); LOGW("Killing the server...");
process_terminate(server->process); process_terminate(server->process);
} }
sc_thread_join(&server->wait_server_thread, NULL); SDL_WaitThread(server->wait_server_thread, NULL);
process_close(server->process); process_close(server->process);
} }
void void
server_destroy(struct server *server) { server_destroy(struct server *server) {
free(server->serial); SDL_free(server->serial);
sc_cond_destroy(&server->process_terminated_cond); SDL_DestroyCond(server->process_terminated_cond);
sc_mutex_destroy(&server->mutex); SDL_DestroyMutex(server->mutex);
} }

View File

@ -6,26 +6,27 @@
#include <stdatomic.h> #include <stdatomic.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <SDL2/SDL_thread.h>
#include "adb.h" #include "adb.h"
#include "scrcpy.h" #include "scrcpy.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h"
struct server { struct server {
char *serial; char *serial;
process_t process; process_t process;
sc_thread wait_server_thread; SDL_Thread *wait_server_thread;
atomic_flag server_socket_closed; atomic_flag server_socket_closed;
sc_mutex mutex; SDL_mutex *mutex;
sc_cond process_terminated_cond; SDL_cond *process_terminated_cond;
bool process_terminated; bool process_terminated;
socket_t server_socket; // only used if !tunnel_forward socket_t server_socket; // only used if !tunnel_forward
socket_t video_socket; socket_t video_socket;
socket_t control_socket; socket_t control_socket;
struct sc_port_range port_range;
uint16_t local_port; // selected from port_range uint16_t local_port; // selected from port_range
bool tunnel_enabled; bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse" bool tunnel_forward; // use "adb forward" instead of "adb reverse"
@ -46,7 +47,6 @@ struct server_params {
bool show_touches; bool show_touches;
bool stay_awake; bool stay_awake;
bool force_adb_forward; bool force_adb_forward;
bool power_off_on_close;
}; };
// init default values // init default values

View File

@ -4,6 +4,8 @@
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <libavutil/time.h> #include <libavutil/time.h>
#include <SDL2/SDL_events.h> #include <SDL2/SDL_events.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include <unistd.h> #include <unistd.h>
#include "decoder.h" #include "decoder.h"
@ -277,8 +279,8 @@ bool
stream_start(struct stream *stream) { stream_start(struct stream *stream) {
LOGD("Starting stream thread"); LOGD("Starting stream thread");
bool ok = sc_thread_create(&stream->thread, run_stream, "stream", stream); stream->thread = SDL_CreateThread(run_stream, "stream", stream);
if (!ok) { if (!stream->thread) {
LOGC("Could not start stream thread"); LOGC("Could not start stream thread");
return false; return false;
} }
@ -294,5 +296,5 @@ stream_stop(struct stream *stream) {
void void
stream_join(struct stream *stream) { stream_join(struct stream *stream) {
sc_thread_join(&stream->thread, NULL); SDL_WaitThread(stream->thread, NULL);
} }

View File

@ -7,13 +7,15 @@
#include <stdint.h> #include <stdint.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <SDL2/SDL_atomic.h> #include <SDL2/SDL_atomic.h>
#include <SDL2/SDL_thread.h>
#include "util/net.h" #include "util/net.h"
#include "util/thread.h"
struct video_buffer;
struct stream { struct stream {
socket_t socket; socket_t socket;
sc_thread thread; SDL_Thread *thread;
struct decoder *decoder; struct decoder *decoder;
struct recorder *recorder; struct recorder *recorder;
AVCodecContext *codec_ctx; AVCodecContext *codec_ctx;

View File

@ -118,11 +118,11 @@ process_terminate(pid_t pid) {
(int) pid); (int) pid);
abort(); abort();
} }
return kill(pid, SIGKILL) != -1; return kill(pid, SIGTERM) != -1;
} }
exit_code_t static bool
process_wait(pid_t pid, bool close) { process_wait_internal(pid_t pid, int *exit_code, bool close) {
int code; int code;
int options = WEXITED; int options = WEXITED;
if (!close) { if (!close) {
@ -133,16 +133,29 @@ process_wait(pid_t pid, bool close) {
int r = waitid(P_PID, pid, &info, options); int r = waitid(P_PID, pid, &info, options);
if (r == -1 || info.si_code != CLD_EXITED) { if (r == -1 || info.si_code != CLD_EXITED) {
// could not wait, or exited unexpectedly, probably by a signal // could not wait, or exited unexpectedly, probably by a signal
code = NO_EXIT_CODE; code = -1;
} else { } else {
code = info.si_status; code = info.si_status;
} }
return code; if (exit_code) {
*exit_code = code;
}
return !code;
}
bool
process_wait(pid_t pid, int *exit_code) {
return process_wait_internal(pid, exit_code, true);
}
bool
process_wait_noclose(pid_t pid, int *exit_code) {
return process_wait_internal(pid, exit_code, false);
} }
void void
process_close(pid_t pid) { process_close(pid_t pid) {
process_wait(pid, true); // ignore exit code process_wait_internal(pid, NULL, true);
} }
char * char *
@ -156,7 +169,7 @@ get_executable_path(void) {
return NULL; return NULL;
} }
buf[len] = '\0'; buf[len] = '\0';
return strdup(buf); return SDL_strdup(buf);
#else #else
// in practice, we only need this feature for portable builds, only used on // in practice, we only need this feature for portable builds, only used on
// Windows, so we don't care implementing it for every platform // Windows, so we don't care implementing it for every platform

View File

@ -41,7 +41,7 @@ process_execute(const char *const argv[], HANDLE *handle) {
if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, 0, NULL, NULL, &si, if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, 0, NULL, NULL, &si,
&pi)) { &pi)) {
free(wide); SDL_free(wide);
*handle = NULL; *handle = NULL;
if (GetLastError() == ERROR_FILE_NOT_FOUND) { if (GetLastError() == ERROR_FILE_NOT_FOUND) {
return PROCESS_ERROR_MISSING_BINARY; return PROCESS_ERROR_MISSING_BINARY;
@ -49,7 +49,7 @@ process_execute(const char *const argv[], HANDLE *handle) {
return PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
free(wide); SDL_free(wide);
*handle = pi.hProcess; *handle = pi.hProcess;
return PROCESS_SUCCESS; return PROCESS_SUCCESS;
} }
@ -59,18 +59,31 @@ process_terminate(HANDLE handle) {
return TerminateProcess(handle, 1); return TerminateProcess(handle, 1);
} }
exit_code_t static bool
process_wait(HANDLE handle, bool close) { process_wait_internal(HANDLE handle, DWORD *exit_code, bool close) {
DWORD code; DWORD code;
if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0
|| !GetExitCodeProcess(handle, &code)) { || !GetExitCodeProcess(handle, &code)) {
// could not wait or retrieve the exit code // could not wait or retrieve the exit code
code = NO_EXIT_CODE; // max value, it's unsigned code = -1; // max value, it's unsigned
}
if (exit_code) {
*exit_code = code;
} }
if (close) { if (close) {
CloseHandle(handle); CloseHandle(handle);
} }
return code; return !code;
}
bool
process_wait(HANDLE handle, DWORD *exit_code) {
return process_wait_internal(handle, exit_code, true);
}
bool
process_wait_noclose(HANDLE handle, DWORD *exit_code) {
return process_wait_internal(handle, exit_code, false);
} }
void void
@ -105,7 +118,7 @@ is_regular_file(const char *path) {
struct _stat path_stat; struct _stat path_stat;
int r = _wstat(wide_path, &path_stat); int r = _wstat(wide_path, &path_stat);
free(wide_path); SDL_free(wide_path);
if (r) { if (r) {
perror("stat"); perror("stat");

View File

@ -33,7 +33,7 @@ buffer_read16be(const uint8_t *buf) {
static inline uint32_t static inline uint32_t
buffer_read32be(const uint8_t *buf) { buffer_read32be(const uint8_t *buf) {
return ((uint32_t) buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
} }
static inline uint64_t static inline uint64_t

75
app/src/util/lock.h Normal file
View File

@ -0,0 +1,75 @@
#ifndef LOCK_H
#define LOCK_H
#include "common.h"
#include <stdint.h>
#include <SDL2/SDL_mutex.h>
#include "log.h"
static inline void
mutex_lock(SDL_mutex *mutex) {
int r = SDL_LockMutex(mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
static inline void
mutex_unlock(SDL_mutex *mutex) {
int r = SDL_UnlockMutex(mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not unlock mutex: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
static inline void
cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
int r = SDL_CondWait(cond, mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not wait on condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
static inline int
cond_wait_timeout(SDL_cond *cond, SDL_mutex *mutex, uint32_t ms) {
int r = SDL_CondWaitTimeout(cond, mutex, ms);
#ifndef NDEBUG
if (r < 0) {
LOGC("Could not wait on condition with timeout: %s", SDL_GetError());
abort();
}
#endif
return r;
}
static inline void
cond_signal(SDL_cond *cond) {
int r = SDL_CondSignal(cond);
#ifndef NDEBUG
if (r) {
LOGC("Could not signal a condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
#endif

View File

@ -3,13 +3,13 @@
#include "log.h" #include "log.h"
bool bool
process_check_success(process_t proc, const char *name, bool close) { process_check_success(process_t proc, const char *name) {
if (proc == PROCESS_NONE) { if (proc == PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name); LOGE("Could not execute \"%s\"", name);
return false; return false;
} }
exit_code_t exit_code = process_wait(proc, close); exit_code_t exit_code;
if (exit_code) { if (!process_wait(proc, &exit_code)) {
if (exit_code != NO_EXIT_CODE) { if (exit_code != NO_EXIT_CODE) {
LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code); LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code);
} else { } else {

View File

@ -47,21 +47,23 @@ bool
process_terminate(process_t pid); process_terminate(process_t pid);
// wait and close the process (like waitpid()) // wait and close the process (like waitpid())
// the "close" flag indicates if the process must be "closed" (reaped) bool
// (passing false is equivalent to enable WNOWAIT in waitid()) process_wait(process_t pid, exit_code_t *exit_code);
exit_code_t
process_wait(process_t pid, bool close); // wait (but does not close) the process (waitid() with WNOWAIT)
bool
process_wait_noclose(process_t pid, exit_code_t *exit_code);
// close the process // close the process
// //
// Semantically, process_wait(close) = process_wait(noclose) + process_close // Semantically, process_wait = process_wait_noclose + process_close.
void void
process_close(process_t pid); process_close(process_t pid);
// convenience function to wait for a successful process execution // convenience function to wait for a successful process execution
// automatically log process errors with the provided process name // automatically log process errors with the provided process name
bool bool
process_check_success(process_t proc, const char *name, bool close); process_check_success(process_t proc, const char *name);
#ifndef _WIN32 #ifndef _WIN32
// only used to find package manager, not implemented for Windows // only used to find package manager, not implemented for Windows
@ -70,7 +72,7 @@ search_executable(const char *file);
#endif #endif
// return the absolute path of the executable (the scrcpy binary) // return the absolute path of the executable (the scrcpy binary)
// may be NULL on error; to be freed by free() // may be NULL on error; to be freed by SDL_free
char * char *
get_executable_path(void); get_executable_path(void);

View File

@ -10,6 +10,8 @@
# include <tchar.h> # include <tchar.h>
#endif #endif
#include <SDL2/SDL_stdinc.h>
size_t size_t
xstrncpy(char *dest, const char *src, size_t n) { xstrncpy(char *dest, const char *src, size_t n) {
size_t i; size_t i;
@ -47,7 +49,7 @@ truncated:
char * char *
strquote(const char *src) { strquote(const char *src) {
size_t len = strlen(src); size_t len = strlen(src);
char *quoted = malloc(len + 3); char *quoted = SDL_malloc(len + 3);
if (!quoted) { if (!quoted) {
return NULL; return NULL;
} }
@ -165,7 +167,7 @@ utf8_to_wide_char(const char *utf8) {
return NULL; return NULL;
} }
wchar_t *wide = malloc(len * sizeof(wchar_t)); wchar_t *wide = SDL_malloc(len * sizeof(wchar_t));
if (!wide) { if (!wide) {
return NULL; return NULL;
} }
@ -181,7 +183,7 @@ utf8_from_wide_char(const wchar_t *ws) {
return NULL; return NULL;
} }
char *utf8 = malloc(len); char *utf8 = SDL_malloc(len);
if (!utf8) { if (!utf8) {
return NULL; return NULL;
} }

View File

@ -1,160 +0,0 @@
#include "thread.h"
#include <assert.h>
#include <SDL2/SDL_thread.h>
#include "log.h"
bool
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
void *userdata) {
SDL_Thread *sdl_thread = SDL_CreateThread(fn, name, userdata);
if (!sdl_thread) {
return false;
}
thread->thread = sdl_thread;
return true;
}
void
sc_thread_join(sc_thread *thread, int *status) {
SDL_WaitThread(thread->thread, status);
}
bool
sc_mutex_init(sc_mutex *mutex) {
SDL_mutex *sdl_mutex = SDL_CreateMutex();
if (!sdl_mutex) {
return false;
}
mutex->mutex = sdl_mutex;
#ifndef NDEBUG
mutex->locker = 0;
#endif
return true;
}
void
sc_mutex_destroy(sc_mutex *mutex) {
SDL_DestroyMutex(mutex->mutex);
}
void
sc_mutex_lock(sc_mutex *mutex) {
// SDL mutexes are recursive, but we don't want to use recursive mutexes
assert(!sc_mutex_held(mutex));
int r = SDL_LockMutex(mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError());
abort();
}
mutex->locker = sc_thread_get_id();
#else
(void) r;
#endif
}
void
sc_mutex_unlock(sc_mutex *mutex) {
#ifndef NDEBUG
assert(sc_mutex_held(mutex));
mutex->locker = 0;
#endif
int r = SDL_UnlockMutex(mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
sc_thread_id
sc_thread_get_id(void) {
return SDL_ThreadID();
}
#ifndef NDEBUG
bool
sc_mutex_held(struct sc_mutex *mutex) {
return mutex->locker == sc_thread_get_id();
}
#endif
bool
sc_cond_init(sc_cond *cond) {
SDL_cond *sdl_cond = SDL_CreateCond();
if (!sdl_cond) {
return false;
}
cond->cond = sdl_cond;
return true;
}
void
sc_cond_destroy(sc_cond *cond) {
SDL_DestroyCond(cond->cond);
}
void
sc_cond_wait(sc_cond *cond, sc_mutex *mutex) {
int r = SDL_CondWait(cond->cond, mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not wait on condition: %s", SDL_GetError());
abort();
}
mutex->locker = sc_thread_get_id();
#else
(void) r;
#endif
}
bool
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms) {
int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms);
#ifndef NDEBUG
if (r < 0) {
LOGC("Could not wait on condition with timeout: %s", SDL_GetError());
abort();
}
mutex->locker = sc_thread_get_id();
#endif
assert(r == 0 || r == SDL_MUTEX_TIMEDOUT);
return r == 0;
}
void
sc_cond_signal(sc_cond *cond) {
int r = SDL_CondSignal(cond->cond);
#ifndef NDEBUG
if (r) {
LOGC("Could not signal a condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
void
sc_cond_broadcast(sc_cond *cond) {
int r = SDL_CondBroadcast(cond->cond);
#ifndef NDEBUG
if (r) {
LOGC("Could not broadcast a condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}

View File

@ -1,81 +0,0 @@
#ifndef SC_THREAD_H
#define SC_THREAD_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
/* Forward declarations */
typedef struct SDL_Thread SDL_Thread;
typedef struct SDL_mutex SDL_mutex;
typedef struct SDL_cond SDL_cond;
typedef int sc_thread_fn(void *);
typedef unsigned int sc_thread_id;
typedef struct sc_thread {
SDL_Thread *thread;
} sc_thread;
typedef struct sc_mutex {
SDL_mutex *mutex;
#ifndef NDEBUG
sc_thread_id locker;
#endif
} sc_mutex;
typedef struct sc_cond {
SDL_cond *cond;
} sc_cond;
bool
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
void *userdata);
void
sc_thread_join(sc_thread *thread, int *status);
bool
sc_mutex_init(sc_mutex *mutex);
void
sc_mutex_destroy(sc_mutex *mutex);
void
sc_mutex_lock(sc_mutex *mutex);
void
sc_mutex_unlock(sc_mutex *mutex);
sc_thread_id
sc_thread_get_id(void);
#ifndef NDEBUG
bool
sc_mutex_held(struct sc_mutex *mutex);
# define sc_mutex_assert(mutex) assert(sc_mutex_held(mutex))
#else
# define sc_mutex_assert(mutex)
#endif
bool
sc_cond_init(sc_cond *cond);
void
sc_cond_destroy(sc_cond *cond);
void
sc_cond_wait(sc_cond *cond, sc_mutex *mutex);
// return true on signaled, false on timeout
bool
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms);
void
sc_cond_signal(sc_cond *cond);
void
sc_cond_broadcast(sc_cond *cond);
#endif

View File

@ -1,147 +1,112 @@
#include "video_buffer.h" #include "video_buffer.h"
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_mutex.h>
#include <libavutil/avutil.h> #include <libavutil/avutil.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
bool bool
video_buffer_init(struct video_buffer *vb, bool wait_consumer) { video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
vb->producer_frame = av_frame_alloc(); bool render_expired_frames) {
if (!vb->producer_frame) { vb->fps_counter = fps_counter;
if (!(vb->decoding_frame = av_frame_alloc())) {
goto error_0; goto error_0;
} }
vb->pending_frame = av_frame_alloc(); if (!(vb->rendering_frame = av_frame_alloc())) {
if (!vb->pending_frame) {
goto error_1; goto error_1;
} }
vb->consumer_frame = av_frame_alloc(); if (!(vb->mutex = SDL_CreateMutex())) {
if (!vb->consumer_frame) {
goto error_2; goto error_2;
} }
bool ok = sc_mutex_init(&vb->mutex); vb->render_expired_frames = render_expired_frames;
if (!ok) { if (render_expired_frames) {
goto error_3; if (!(vb->rendering_frame_consumed_cond = SDL_CreateCond())) {
} SDL_DestroyMutex(vb->mutex);
vb->wait_consumer = wait_consumer;
if (wait_consumer) {
ok = sc_cond_init(&vb->pending_frame_consumed_cond);
if (!ok) {
sc_mutex_destroy(&vb->mutex);
goto error_2; goto error_2;
} }
// interrupted is not used if wait_consumer is disabled since offering // interrupted is not used if expired frames are not rendered
// a frame will never block // since offering a frame will never block
vb->interrupted = false; vb->interrupted = false;
} }
// there is initially no frame, so consider it has already been consumed // there is initially no rendering frame, so consider it has already been
vb->pending_frame_consumed = true; // consumed
vb->rendering_frame_consumed = true;
// The callbacks must be set by the consumer via
// video_buffer_set_consumer_callbacks()
vb->cbs = NULL;
return true; return true;
error_3:
av_frame_free(&vb->consumer_frame);
error_2: error_2:
av_frame_free(&vb->pending_frame); av_frame_free(&vb->rendering_frame);
error_1: error_1:
av_frame_free(&vb->producer_frame); av_frame_free(&vb->decoding_frame);
error_0: error_0:
return false; return false;
} }
void void
video_buffer_destroy(struct video_buffer *vb) { video_buffer_destroy(struct video_buffer *vb) {
if (vb->wait_consumer) { if (vb->render_expired_frames) {
sc_cond_destroy(&vb->pending_frame_consumed_cond); SDL_DestroyCond(vb->rendering_frame_consumed_cond);
} }
sc_mutex_destroy(&vb->mutex); SDL_DestroyMutex(vb->mutex);
av_frame_free(&vb->consumer_frame); av_frame_free(&vb->rendering_frame);
av_frame_free(&vb->pending_frame); av_frame_free(&vb->decoding_frame);
av_frame_free(&vb->producer_frame);
} }
static inline void static void
swap_frames(AVFrame **lhs, AVFrame **rhs) { video_buffer_swap_frames(struct video_buffer *vb) {
AVFrame *tmp = *lhs; AVFrame *tmp = vb->decoding_frame;
*lhs = *rhs; vb->decoding_frame = vb->rendering_frame;
*rhs = tmp; vb->rendering_frame = tmp;
} }
void void
video_buffer_set_consumer_callbacks(struct video_buffer *vb, video_buffer_offer_decoded_frame(struct video_buffer *vb,
const struct video_buffer_callbacks *cbs, bool *previous_frame_skipped) {
void *cbs_userdata) { mutex_lock(vb->mutex);
assert(!vb->cbs); // must be set only once if (vb->render_expired_frames) {
assert(cbs);
assert(cbs->on_frame_available);
vb->cbs = cbs;
vb->cbs_userdata = cbs_userdata;
}
void
video_buffer_producer_offer_frame(struct video_buffer *vb) {
assert(vb->cbs);
sc_mutex_lock(&vb->mutex);
if (vb->wait_consumer) {
// wait for the current (expired) frame to be consumed // wait for the current (expired) frame to be consumed
while (!vb->pending_frame_consumed && !vb->interrupted) { while (!vb->rendering_frame_consumed && !vb->interrupted) {
sc_cond_wait(&vb->pending_frame_consumed_cond, &vb->mutex); cond_wait(vb->rendering_frame_consumed_cond, vb->mutex);
} }
} else if (!vb->rendering_frame_consumed) {
fps_counter_add_skipped_frame(vb->fps_counter);
} }
av_frame_unref(vb->pending_frame); video_buffer_swap_frames(vb);
swap_frames(&vb->producer_frame, &vb->pending_frame);
bool skipped = !vb->pending_frame_consumed; *previous_frame_skipped = !vb->rendering_frame_consumed;
vb->pending_frame_consumed = false; vb->rendering_frame_consumed = false;
sc_mutex_unlock(&vb->mutex); mutex_unlock(vb->mutex);
if (skipped) {
if (vb->cbs->on_frame_skipped)
vb->cbs->on_frame_skipped(vb, vb->cbs_userdata);
} else {
vb->cbs->on_frame_available(vb, vb->cbs_userdata);
}
} }
const AVFrame * const AVFrame *
video_buffer_consumer_take_frame(struct video_buffer *vb) { video_buffer_consume_rendered_frame(struct video_buffer *vb) {
sc_mutex_lock(&vb->mutex); assert(!vb->rendering_frame_consumed);
assert(!vb->pending_frame_consumed); vb->rendering_frame_consumed = true;
vb->pending_frame_consumed = true; fps_counter_add_rendered_frame(vb->fps_counter);
if (vb->render_expired_frames) {
swap_frames(&vb->consumer_frame, &vb->pending_frame);
av_frame_unref(vb->pending_frame);
if (vb->wait_consumer) {
// unblock video_buffer_offer_decoded_frame() // unblock video_buffer_offer_decoded_frame()
sc_cond_signal(&vb->pending_frame_consumed_cond); cond_signal(vb->rendering_frame_consumed_cond);
} }
sc_mutex_unlock(&vb->mutex); return vb->rendering_frame;
// consumer_frame is only written from this thread, no need to lock
return vb->consumer_frame;
} }
void void
video_buffer_interrupt(struct video_buffer *vb) { video_buffer_interrupt(struct video_buffer *vb) {
if (vb->wait_consumer) { if (vb->render_expired_frames) {
sc_mutex_lock(&vb->mutex); mutex_lock(vb->mutex);
vb->interrupted = true; vb->interrupted = true;
sc_mutex_unlock(&vb->mutex); mutex_unlock(vb->mutex);
// wake up blocking wait // wake up blocking wait
sc_cond_signal(&vb->pending_frame_consumed_cond); cond_signal(vb->rendering_frame_consumed_cond);
} }
} }

View File

@ -4,76 +4,44 @@
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include "fps_counter.h" #include "fps_counter.h"
#include "util/thread.h"
// forward declarations // forward declarations
typedef struct AVFrame AVFrame; typedef struct AVFrame AVFrame;
/**
* There are 3 frames in memory:
* - one frame is held by the producer (producer_frame)
* - one frame is held by the consumer (consumer_frame)
* - one frame is shared between the producer and the consumer (pending_frame)
*
* The producer generates a frame into the producer_frame (it may takes time).
*
* Once the frame is produced, it calls video_buffer_producer_offer_frame(),
* which swaps the producer and pending frames.
*
* When the consumer is notified that a new frame is available, it calls
* video_buffer_consumer_take_frame() to retrieve it, which swaps the pending
* and consumer frames. The frame is valid until the next call, without
* blocking the producer.
*/
struct video_buffer { struct video_buffer {
AVFrame *producer_frame; AVFrame *decoding_frame;
AVFrame *pending_frame; AVFrame *rendering_frame;
AVFrame *consumer_frame; SDL_mutex *mutex;
bool render_expired_frames;
sc_mutex mutex;
bool wait_consumer; // never overwrite a pending frame if it is not consumed
bool interrupted; bool interrupted;
SDL_cond *rendering_frame_consumed_cond;
sc_cond pending_frame_consumed_cond; bool rendering_frame_consumed;
bool pending_frame_consumed; struct fps_counter *fps_counter;
const struct video_buffer_callbacks *cbs;
void *cbs_userdata;
};
struct video_buffer_callbacks {
// Called when a new frame can be consumed by
// video_buffer_consumer_take_frame(vb)
// This callback is mandatory (it must not be NULL).
void (*on_frame_available)(struct video_buffer *vb, void *userdata);
// Called when a pending frame has been overwritten by the producer
// This callback is optional (it may be NULL).
void (*on_frame_skipped)(struct video_buffer *vb, void *userdata);
}; };
bool bool
video_buffer_init(struct video_buffer *vb, bool wait_consumer); video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
bool render_expired_frames);
void void
video_buffer_destroy(struct video_buffer *vb); video_buffer_destroy(struct video_buffer *vb);
// set the decoded frame as ready for rendering
// this function locks frames->mutex during its execution
// the output flag is set to report whether the previous frame has been skipped
void void
video_buffer_set_consumer_callbacks(struct video_buffer *vb, video_buffer_offer_decoded_frame(struct video_buffer *vb,
const struct video_buffer_callbacks *cbs, bool *previous_frame_skipped);
void *cbs_userdata);
// set the producer frame as ready for consuming // mark the rendering frame as consumed and return it
void // MUST be called with frames->mutex locked!!!
video_buffer_producer_offer_frame(struct video_buffer *vb); // the caller is expected to render the returned frame to some texture before
// unlocking frames->mutex
// mark the consumer frame as consumed and return it
// the frame is valid until the next call to this function
const AVFrame * const AVFrame *
video_buffer_consumer_take_frame(struct video_buffer *vb); video_buffer_consume_rendered_frame(struct video_buffer *vb);
// wake up and avoid any blocking call // wake up and avoid any blocking call
void void

View File

@ -17,7 +17,7 @@ static void test_serialize_inject_keycode(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 14); assert(size == 14);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -39,7 +39,7 @@ static void test_serialize_inject_text(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 18); assert(size == 18);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -59,7 +59,7 @@ static void test_serialize_inject_text_long(void) {
msg.inject_text.text = text; msg.inject_text.text = text;
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH]; unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
@ -95,7 +95,7 @@ static void test_serialize_inject_touch_event(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 28); assert(size == 28);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -130,7 +130,7 @@ static void test_serialize_inject_scroll_event(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 21); assert(size == 21);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -146,18 +146,14 @@ static void test_serialize_inject_scroll_event(void) {
static void test_serialize_back_or_screen_on(void) { static void test_serialize_back_or_screen_on(void) {
struct control_msg msg = { struct control_msg msg = {
.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, .type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
.back_or_screen_on = {
.action = AKEY_EVENT_ACTION_UP,
},
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 2); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON, CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
0x01, // AKEY_EVENT_ACTION_UP
}; };
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
} }
@ -168,7 +164,7 @@ static void test_serialize_expand_notification_panel(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -177,32 +173,17 @@ static void test_serialize_expand_notification_panel(void) {
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
} }
static void test_serialize_expand_settings_panel(void) { static void test_serialize_collapse_notification_panel(void) {
struct control_msg msg = { struct control_msg msg = {
.type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL, .type = CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {
CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL, CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_collapse_panels(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS,
};
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 1);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_COLLAPSE_PANELS,
}; };
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
} }
@ -213,7 +194,7 @@ static void test_serialize_get_clipboard(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -232,7 +213,7 @@ static void test_serialize_set_clipboard(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 19); assert(size == 19);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -253,7 +234,7 @@ static void test_serialize_set_screen_power_mode(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 2); assert(size == 2);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -269,7 +250,7 @@ static void test_serialize_rotate_device(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {
@ -289,8 +270,7 @@ int main(int argc, char *argv[]) {
test_serialize_inject_scroll_event(); test_serialize_inject_scroll_event();
test_serialize_back_or_screen_on(); test_serialize_back_or_screen_on();
test_serialize_expand_notification_panel(); test_serialize_expand_notification_panel();
test_serialize_expand_settings_panel(); test_serialize_collapse_notification_panel();
test_serialize_collapse_panels();
test_serialize_get_clipboard(); test_serialize_get_clipboard();
test_serialize_set_clipboard(); test_serialize_set_clipboard();
test_serialize_set_screen_power_mode(); test_serialize_set_screen_power_mode();

View File

@ -4,6 +4,7 @@
#include <limits.h> #include <limits.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <SDL2/SDL.h>
#include "util/str_util.h" #include "util/str_util.h"
@ -137,7 +138,7 @@ static void test_strquote(void) {
// add '"' at the beginning and the end // add '"' at the beginning and the end
assert(!strcmp("\"abcde\"", out)); assert(!strcmp("\"abcde\"", out));
free(out); SDL_free(out);
} }
static void test_utf8_truncate(void) { static void test_utf8_truncate(void) {

View File

@ -2,11 +2,11 @@
[binaries] [binaries]
name = 'mingw' name = 'mingw'
c = 'i686-w64-mingw32-gcc' c = '/usr/bin/i686-w64-mingw32-gcc'
cpp = 'i686-w64-mingw32-g++' cpp = '/usr/bin/i686-w64-mingw32-g++'
ar = 'i686-w64-mingw32-ar' ar = '/usr/bin/i686-w64-mingw32-ar'
strip = 'i686-w64-mingw32-strip' strip = '/usr/bin/i686-w64-mingw32-strip'
pkgconfig = 'i686-w64-mingw32-pkg-config' pkgconfig = '/usr/bin/i686-w64-mingw32-pkg-config'
[host_machine] [host_machine]
system = 'windows' system = 'windows'

View File

@ -2,11 +2,11 @@
[binaries] [binaries]
name = 'mingw' name = 'mingw'
c = 'x86_64-w64-mingw32-gcc' c = '/usr/bin/x86_64-w64-mingw32-gcc'
cpp = 'x86_64-w64-mingw32-g++' cpp = '/usr/bin/x86_64-w64-mingw32-g++'
ar = 'x86_64-w64-mingw32-ar' ar = '/usr/bin/x86_64-w64-mingw32-ar'
strip = 'x86_64-w64-mingw32-strip' strip = '/usr/bin/x86_64-w64-mingw32-strip'
pkgconfig = 'x86_64-w64-mingw32-pkg-config' pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config'
[host_machine] [host_machine]
system = 'windows' system = 'windows'

View File

@ -1,7 +1 @@
strCommand = "cmd /c scrcpy.exe" CreateObject("Wscript.Shell").Run "cmd /c scrcpy.exe", 0, false
For Each Arg In WScript.Arguments
strCommand = strCommand & " """ & replace(Arg, """", """""""""") & """"
Next
CreateObject("Wscript.Shell").Run strCommand, 0, false

View File

@ -4,7 +4,6 @@ project('scrcpy', 'c',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',
'warning_level=2', 'warning_level=2',
'b_ndebug=if-release',
]) ])
if get_option('compile_app') if get_option('compile_app')

View File

@ -3,5 +3,6 @@ option('compile_server', type: 'boolean', value: true, description: 'Build the s
option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux') option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux')
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server') option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable') option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable')
option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support')
option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached') option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached')
option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")') option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")')

View File

@ -19,21 +19,19 @@ public final class CleanUp {
// not instantiable // not instantiable
} }
public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode, boolean powerOffScreen, int displayId) public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException {
throws IOException { boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode;
boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode || powerOffScreen;
if (needProcess) { if (needProcess) {
startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode, powerOffScreen, displayId); startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode);
} else { } else {
// There is no additional clean up to do when scrcpy dies // There is no additional clean up to do when scrcpy dies
unlinkSelf(); unlinkSelf();
} }
} }
private static void startProcess(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode, boolean powerOffScreen, private static void startProcess(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException {
int displayId) throws IOException {
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf( String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf(
restoreStayOn), String.valueOf(restoreNormalPowerMode), String.valueOf(powerOffScreen), String.valueOf(displayId)}; restoreStayOn), String.valueOf(restoreNormalPowerMode)};
ProcessBuilder builder = new ProcessBuilder(cmd); ProcessBuilder builder = new ProcessBuilder(cmd);
builder.environment().put("CLASSPATH", SERVER_PATH); builder.environment().put("CLASSPATH", SERVER_PATH);
@ -63,8 +61,6 @@ public final class CleanUp {
boolean disableShowTouches = Boolean.parseBoolean(args[0]); boolean disableShowTouches = Boolean.parseBoolean(args[0]);
int restoreStayOn = Integer.parseInt(args[1]); int restoreStayOn = Integer.parseInt(args[1]);
boolean restoreNormalPowerMode = Boolean.parseBoolean(args[2]); boolean restoreNormalPowerMode = Boolean.parseBoolean(args[2]);
boolean powerOffScreen = Boolean.parseBoolean(args[3]);
int displayId = Integer.parseInt(args[4]);
if (disableShowTouches || restoreStayOn != -1) { if (disableShowTouches || restoreStayOn != -1) {
ServiceManager serviceManager = new ServiceManager(); ServiceManager serviceManager = new ServiceManager();
@ -80,12 +76,9 @@ public final class CleanUp {
} }
} }
if (Device.isScreenOn()) { if (restoreNormalPowerMode) {
if (powerOffScreen) {
Ln.i("Power off screen");
Device.powerOffScreen(displayId);
} else if (restoreNormalPowerMode) {
Ln.i("Restoring normal power mode"); Ln.i("Restoring normal power mode");
if (Device.isScreenOn()) {
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL); Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
} }
} }

View File

@ -11,12 +11,11 @@ public final class ControlMessage {
public static final int TYPE_INJECT_SCROLL_EVENT = 3; public static final int TYPE_INJECT_SCROLL_EVENT = 3;
public static final int TYPE_BACK_OR_SCREEN_ON = 4; public static final int TYPE_BACK_OR_SCREEN_ON = 4;
public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5; public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5;
public static final int TYPE_EXPAND_SETTINGS_PANEL = 6; public static final int TYPE_COLLAPSE_NOTIFICATION_PANEL = 6;
public static final int TYPE_COLLAPSE_PANELS = 7; public static final int TYPE_GET_CLIPBOARD = 7;
public static final int TYPE_GET_CLIPBOARD = 8; public static final int TYPE_SET_CLIPBOARD = 8;
public static final int TYPE_SET_CLIPBOARD = 9; public static final int TYPE_SET_SCREEN_POWER_MODE = 9;
public static final int TYPE_SET_SCREEN_POWER_MODE = 10; public static final int TYPE_ROTATE_DEVICE = 10;
public static final int TYPE_ROTATE_DEVICE = 11;
private int type; private int type;
private String text; private String text;
@ -72,13 +71,6 @@ public final class ControlMessage {
return msg; return msg;
} }
public static ControlMessage createBackOrScreenOn(int action) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_BACK_OR_SCREEN_ON;
msg.action = action;
return msg;
}
public static ControlMessage createSetClipboard(String text, boolean paste) { public static ControlMessage createSetClipboard(String text, boolean paste) {
ControlMessage msg = new ControlMessage(); ControlMessage msg = new ControlMessage();
msg.type = TYPE_SET_CLIPBOARD; msg.type = TYPE_SET_CLIPBOARD;

View File

@ -11,7 +11,6 @@ public class ControlMessageReader {
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13; static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27; static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
static final int BACK_OR_SCREEN_ON_LENGTH = 1;
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1; static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1;
@ -67,18 +66,15 @@ public class ControlMessageReader {
case ControlMessage.TYPE_INJECT_SCROLL_EVENT: case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
msg = parseInjectScrollEvent(); msg = parseInjectScrollEvent();
break; break;
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
msg = parseBackOrScreenOnEvent();
break;
case ControlMessage.TYPE_SET_CLIPBOARD: case ControlMessage.TYPE_SET_CLIPBOARD:
msg = parseSetClipboard(); msg = parseSetClipboard();
break; break;
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
msg = parseSetScreenPowerMode(); msg = parseSetScreenPowerMode();
break; break;
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL: case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
case ControlMessage.TYPE_COLLAPSE_PANELS:
case ControlMessage.TYPE_GET_CLIPBOARD: case ControlMessage.TYPE_GET_CLIPBOARD:
case ControlMessage.TYPE_ROTATE_DEVICE: case ControlMessage.TYPE_ROTATE_DEVICE:
msg = ControlMessage.createEmpty(type); msg = ControlMessage.createEmpty(type);
@ -154,14 +150,6 @@ public class ControlMessageReader {
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll); return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll);
} }
private ControlMessage parseBackOrScreenOnEvent() {
if (buffer.remaining() < BACK_OR_SCREEN_ON_LENGTH) {
return null;
}
int action = toUnsigned(buffer.get());
return ControlMessage.createBackOrScreenOn(action);
}
private ControlMessage parseSetClipboard() { private ControlMessage parseSetClipboard() {
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) { if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
return null; return null;

View File

@ -14,7 +14,7 @@ import java.util.concurrent.TimeUnit;
public class Controller { public class Controller {
private static final int DEFAULT_DEVICE_ID = 0; private static final int DEVICE_ID_VIRTUAL = -1;
private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor(); private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor();
@ -45,7 +45,7 @@ public class Controller {
MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords(); MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
coords.orientation = 0; coords.orientation = 0;
coords.size = 0; coords.size = 1;
pointerProperties[i] = props; pointerProperties[i] = props;
pointerCoords[i] = coords; pointerCoords[i] = coords;
@ -101,16 +101,13 @@ public class Controller {
break; break;
case ControlMessage.TYPE_BACK_OR_SCREEN_ON: case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
if (device.supportsInputEvents()) { if (device.supportsInputEvents()) {
pressBackOrTurnScreenOn(msg.getAction()); pressBackOrTurnScreenOn();
} }
break; break;
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
Device.expandNotificationPanel(); Device.expandNotificationPanel();
break; break;
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL: case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
Device.expandSettingsPanel();
break;
case ControlMessage.TYPE_COLLAPSE_PANELS:
Device.collapsePanels(); Device.collapsePanels();
break; break;
case ControlMessage.TYPE_GET_CLIPBOARD: case ControlMessage.TYPE_GET_CLIPBOARD:
@ -211,13 +208,9 @@ public class Controller {
// Right-click and middle-click only work if the source is a mouse // Right-click and middle-click only work if the source is a mouse
boolean nonPrimaryButtonPressed = (buttons & ~MotionEvent.BUTTON_PRIMARY) != 0; boolean nonPrimaryButtonPressed = (buttons & ~MotionEvent.BUTTON_PRIMARY) != 0;
int source = nonPrimaryButtonPressed ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_TOUCHSCREEN; int source = nonPrimaryButtonPressed ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_TOUCHSCREEN;
if (source != InputDevice.SOURCE_MOUSE) {
// Buttons must not be set for touch events
buttons = 0;
}
MotionEvent event = MotionEvent MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, .obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0, source,
0); 0);
return device.injectEvent(event); return device.injectEvent(event);
} }
@ -240,7 +233,7 @@ public class Controller {
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll); coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
MotionEvent event = MotionEvent MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0, .obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
InputDevice.SOURCE_TOUCHSCREEN, 0); InputDevice.SOURCE_TOUCHSCREEN, 0);
return device.injectEvent(event); return device.injectEvent(event);
} }
@ -258,22 +251,12 @@ public class Controller {
}, 200, TimeUnit.MILLISECONDS); }, 200, TimeUnit.MILLISECONDS);
} }
private boolean pressBackOrTurnScreenOn(int action) { private boolean pressBackOrTurnScreenOn() {
if (Device.isScreenOn()) { int keycode = Device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER;
return device.injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0); if (keepPowerModeOff && keycode == KeyEvent.KEYCODE_POWER) {
}
// Screen is off
// Only press POWER on ACTION_DOWN
if (action != KeyEvent.ACTION_DOWN) {
// do nothing,
return true;
}
if (keepPowerModeOff) {
schedulePowerModeOff(); schedulePowerModeOff();
} }
return device.injectKeycode(KeyEvent.KEYCODE_POWER); return device.injectKeycode(keycode);
} }
private boolean setClipboard(String text, boolean paste) { private boolean setClipboard(String text, boolean paste) {

View File

@ -153,17 +153,13 @@ public final class Device {
return Build.MODEL; return Build.MODEL;
} }
public static boolean supportsInputEvents(int displayId) {
return displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
}
public boolean supportsInputEvents() { public boolean supportsInputEvents() {
return supportsInputEvents; return supportsInputEvents;
} }
public static boolean injectEvent(InputEvent inputEvent, int mode, int displayId) { public boolean injectEvent(InputEvent inputEvent, int mode) {
if (!supportsInputEvents(displayId)) { if (!supportsInputEvents()) {
return false; throw new AssertionError("Could not inject input event if !supportsInputEvents()");
} }
if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) { if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) {
@ -173,29 +169,10 @@ public final class Device {
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, mode); return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, mode);
} }
public boolean injectEvent(InputEvent inputEvent, int mode) {
if (!supportsInputEvents()) {
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
}
return injectEvent(inputEvent, mode, displayId);
}
public static boolean injectEventOnDisplay(InputEvent event, int displayId) {
return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, displayId);
}
public boolean injectEvent(InputEvent event) { public boolean injectEvent(InputEvent event) {
return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
} }
public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId) {
long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
return injectEventOnDisplay(event, displayId);
}
public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) { public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
long now = SystemClock.uptimeMillis(); long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
@ -203,10 +180,6 @@ public final class Device {
return injectEvent(event); return injectEvent(event);
} }
public static boolean injectKeycode(int keyCode, int displayId) {
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0, displayId) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId);
}
public boolean injectKeycode(int keyCode) { public boolean injectKeycode(int keyCode) {
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0); return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
} }
@ -227,10 +200,6 @@ public final class Device {
SERVICE_MANAGER.getStatusBarManager().expandNotificationsPanel(); SERVICE_MANAGER.getStatusBarManager().expandNotificationsPanel();
} }
public static void expandSettingsPanel() {
SERVICE_MANAGER.getStatusBarManager().expandSettingsPanel();
}
public static void collapsePanels() { public static void collapsePanels() {
SERVICE_MANAGER.getStatusBarManager().collapsePanels(); SERVICE_MANAGER.getStatusBarManager().collapsePanels();
} }
@ -280,13 +249,6 @@ public final class Device {
return SurfaceControl.setDisplayPowerMode(d, mode); return SurfaceControl.setDisplayPowerMode(d, mode);
} }
public static boolean powerOffScreen(int displayId) {
if (!isScreenOn()) {
return true;
}
return injectKeycode(KeyEvent.KEYCODE_POWER, displayId);
}
/** /**
* Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled). * Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled).
*/ */

View File

@ -17,7 +17,6 @@ public class Options {
private boolean stayAwake; private boolean stayAwake;
private String codecOptions; private String codecOptions;
private String encoderName; private String encoderName;
private boolean powerOffScreenOnClose;
public Ln.Level getLogLevel() { public Ln.Level getLogLevel() {
return logLevel; return logLevel;
@ -130,12 +129,4 @@ public class Options {
public void setEncoderName(String encoderName) { public void setEncoderName(String encoderName) {
this.encoderName = encoderName; this.encoderName = encoderName;
} }
public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) {
this.powerOffScreenOnClose = powerOffScreenOnClose;
}
public boolean getPowerOffScreenOnClose() {
return this.powerOffScreenOnClose;
}
} }

View File

@ -50,7 +50,7 @@ public final class Server {
} }
} }
CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn, true, options.getPowerOffScreenOnClose(), options.getDisplayId()); CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn, true);
boolean tunnelForward = options.isTunnelForward(); boolean tunnelForward = options.isTunnelForward();
@ -135,7 +135,7 @@ public final class Server {
"The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")");
} }
final int expectedParameters = 16; final int expectedParameters = 15;
if (args.length != expectedParameters) { if (args.length != expectedParameters) {
throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters"); throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters");
} }
@ -185,9 +185,6 @@ public final class Server {
String encoderName = "-".equals(args[14]) ? null : args[14]; String encoderName = "-".equals(args[14]) ? null : args[14];
options.setEncoderName(encoderName); options.setEncoderName(encoderName);
boolean powerOffScreenOnClose = Boolean.parseBoolean(args[15]);
options.setPowerOffScreenOnClose(powerOffScreenOnClose);
return options; return options;
} }
@ -233,7 +230,7 @@ public final class Server {
if (encoders != null && encoders.length > 0) { if (encoders != null && encoders.length > 0) {
Ln.e("Try to use one of the available encoders:"); Ln.e("Try to use one of the available encoders:");
for (MediaCodecInfo encoder : encoders) { for (MediaCodecInfo encoder : encoders) {
Ln.e(" scrcpy --encoder '" + encoder.getName() + "'"); Ln.e(" scrcpy --encoder-name '" + encoder.getName() + "'");
} }
} }
} }

View File

@ -14,7 +14,7 @@ public class ActivityManager {
private final IInterface manager; private final IInterface manager;
private Method getContentProviderExternalMethod; private Method getContentProviderExternalMethod;
private boolean getContentProviderExternalMethodNewVersion = true; private boolean getContentProviderExternalMethodLegacy;
private Method removeContentProviderExternalMethod; private Method removeContentProviderExternalMethod;
public ActivityManager(IInterface manager) { public ActivityManager(IInterface manager) {
@ -29,7 +29,7 @@ public class ActivityManager {
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
// old version // old version
getContentProviderExternalMethod = manager.getClass().getMethod("getContentProviderExternal", String.class, int.class, IBinder.class); getContentProviderExternalMethod = manager.getClass().getMethod("getContentProviderExternal", String.class, int.class, IBinder.class);
getContentProviderExternalMethodNewVersion = false; getContentProviderExternalMethodLegacy = true;
} }
} }
return getContentProviderExternalMethod; return getContentProviderExternalMethod;
@ -46,7 +46,7 @@ public class ActivityManager {
try { try {
Method method = getGetContentProviderExternalMethod(); Method method = getGetContentProviderExternalMethod();
Object[] args; Object[] args;
if (getContentProviderExternalMethodNewVersion) { if (!getContentProviderExternalMethodLegacy) {
// new version // new version
args = new Object[]{name, ServiceManager.USER_ID, token, null}; args = new Object[]{name, ServiceManager.USER_ID, token, null};
} else { } else {

View File

@ -11,8 +11,6 @@ public class StatusBarManager {
private final IInterface manager; private final IInterface manager;
private Method expandNotificationsPanelMethod; private Method expandNotificationsPanelMethod;
private Method expandSettingsPanelMethod;
private boolean expandSettingsPanelMethodNewVersion = true;
private Method collapsePanelsMethod; private Method collapsePanelsMethod;
public StatusBarManager(IInterface manager) { public StatusBarManager(IInterface manager) {
@ -26,20 +24,6 @@ public class StatusBarManager {
return expandNotificationsPanelMethod; return expandNotificationsPanelMethod;
} }
private Method getExpandSettingsPanel() throws NoSuchMethodException {
if (expandSettingsPanelMethod == null) {
try {
// Since Android 7: https://android.googlesource.com/platform/frameworks/base.git/+/a9927325eda025504d59bb6594fee8e240d95b01%5E%21/
expandSettingsPanelMethod = manager.getClass().getMethod("expandSettingsPanel", String.class);
} catch (NoSuchMethodException e) {
// old version
expandSettingsPanelMethod = manager.getClass().getMethod("expandSettingsPanel");
expandSettingsPanelMethodNewVersion = false;
}
}
return expandSettingsPanelMethod;
}
private Method getCollapsePanelsMethod() throws NoSuchMethodException { private Method getCollapsePanelsMethod() throws NoSuchMethodException {
if (collapsePanelsMethod == null) { if (collapsePanelsMethod == null) {
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels"); collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
@ -56,21 +40,6 @@ public class StatusBarManager {
} }
} }
public void expandSettingsPanel() {
try {
Method method = getExpandSettingsPanel();
if (expandSettingsPanelMethodNewVersion) {
// new version
method.invoke(manager, (Object) null);
} else {
// old version
method.invoke(manager);
}
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Could not invoke method", e);
}
}
public void collapsePanels() { public void collapsePanels() {
try { try {
Method method = getCollapsePanelsMethod(); Method method = getCollapsePanelsMethod();

View File

@ -154,7 +154,6 @@ public class ControlMessageReaderTest {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos); DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_BACK_OR_SCREEN_ON); dos.writeByte(ControlMessage.TYPE_BACK_OR_SCREEN_ON);
dos.writeByte(KeyEvent.ACTION_UP);
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
@ -162,7 +161,6 @@ public class ControlMessageReaderTest {
ControlMessage event = reader.next(); ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_BACK_OR_SCREEN_ON, event.getType()); Assert.assertEquals(ControlMessage.TYPE_BACK_OR_SCREEN_ON, event.getType());
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
} }
@Test @Test
@ -182,35 +180,19 @@ public class ControlMessageReaderTest {
} }
@Test @Test
public void testParseExpandSettingsPanelEvent() throws IOException { public void testParseCollapseNotificationPanelEvent() throws IOException {
ControlMessageReader reader = new ControlMessageReader(); ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos); DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_EXPAND_SETTINGS_PANEL); dos.writeByte(ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL);
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet)); reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next(); ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_EXPAND_SETTINGS_PANEL, event.getType()); Assert.assertEquals(ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL, event.getType());
}
@Test
public void testParseCollapsePanelsEvent() throws IOException {
ControlMessageReader reader = new ControlMessageReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_COLLAPSE_PANELS);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next();
Assert.assertEquals(ControlMessage.TYPE_COLLAPSE_PANELS, event.getType());
} }
@Test @Test