Compare commits
46 Commits
ffmpeg5
...
raw_video_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93ac6a347e | ||
|
|
45a5e560df | ||
|
|
3ba32c2a0d | ||
|
|
6b21f4ae13 | ||
|
|
31a5d0c2bf | ||
|
|
f289d206ea | ||
|
|
ca516f4318 | ||
|
|
5d6076bffd | ||
|
|
e0bce1725b | ||
|
|
a9429efa34 | ||
|
|
063d103dd6 | ||
|
|
4bf9c057fe | ||
|
|
17c97820b2 | ||
|
|
8c7f0ed5ea | ||
|
|
ac038f276e | ||
|
|
1f65b1bf87 | ||
|
|
d41a46dc95 | ||
|
|
308a1f8192 | ||
|
|
241a587e61 | ||
|
|
7e35bfe382 | ||
|
|
855819bbd8 | ||
|
|
557daf280e | ||
|
|
0b8e926330 | ||
|
|
0ec3361bc9 | ||
|
|
81ff7ebd06 | ||
|
|
1ffe312369 | ||
|
|
ebef027c4f | ||
|
|
8e4e7d42f1 | ||
|
|
b066dc0bbf | ||
|
|
262506c733 | ||
|
|
2eb6fe7d81 | ||
|
|
3a0ba7d0a4 | ||
|
|
75c5dc6859 | ||
|
|
fa30f9806a | ||
|
|
4fb61ac83d | ||
|
|
8fa9e6b01a | ||
|
|
0ec64baad4 | ||
|
|
15bf27afdd | ||
|
|
26b4104844 | ||
|
|
723faa5dee | ||
|
|
162043911e | ||
|
|
117fe32626 | ||
|
|
b7a06278fe | ||
|
|
b3ff1f6b3b | ||
|
|
a2495c5ef1 | ||
|
|
37c7827d46 |
3
FAQ.md
3
FAQ.md
@@ -219,6 +219,9 @@ scrcpy -m 1024
|
|||||||
scrcpy -m 800
|
scrcpy -m 800
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Since scrcpy v1.22, scrcpy automatically tries again with a lower definition
|
||||||
|
before failing. This behavior can be disabled with `--no-downsize-on-error`.
|
||||||
|
|
||||||
You could also try another [encoder](README.md#encoder).
|
You could also try another [encoder](README.md#encoder).
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ src = [
|
|||||||
'src/decoder.c',
|
'src/decoder.c',
|
||||||
'src/device_msg.c',
|
'src/device_msg.c',
|
||||||
'src/icon.c',
|
'src/icon.c',
|
||||||
'src/file_handler.c',
|
'src/file_pusher.c',
|
||||||
'src/fps_counter.c',
|
'src/fps_counter.c',
|
||||||
'src/frame_buffer.c',
|
'src/frame_buffer.c',
|
||||||
'src/input_manager.c',
|
'src/input_manager.c',
|
||||||
@@ -118,15 +118,20 @@ else
|
|||||||
include_directories: include_directories(sdl2_include_dir)
|
include_directories: include_directories(sdl2_include_dir)
|
||||||
)
|
)
|
||||||
|
|
||||||
prebuilt_ffmpeg_shared = meson.get_cross_property('prebuilt_ffmpeg_shared')
|
prebuilt_ffmpeg = meson.get_cross_property('prebuilt_ffmpeg')
|
||||||
prebuilt_ffmpeg_dev = meson.get_cross_property('prebuilt_ffmpeg_dev')
|
ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg + '/bin'
|
||||||
ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg_shared + '/bin'
|
ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg + '/include'
|
||||||
ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg_dev + '/include'
|
|
||||||
|
# ffmpeg versions are different for win32 and win64 builds
|
||||||
|
ffmpeg_avcodec = meson.get_cross_property('ffmpeg_avcodec')
|
||||||
|
ffmpeg_avformat = meson.get_cross_property('ffmpeg_avformat')
|
||||||
|
ffmpeg_avutil = meson.get_cross_property('ffmpeg_avutil')
|
||||||
|
|
||||||
ffmpeg = declare_dependency(
|
ffmpeg = declare_dependency(
|
||||||
dependencies: [
|
dependencies: [
|
||||||
cc.find_library('avcodec-58', dirs: ffmpeg_bin_dir),
|
cc.find_library(ffmpeg_avcodec, dirs: ffmpeg_bin_dir),
|
||||||
cc.find_library('avformat-58', dirs: ffmpeg_bin_dir),
|
cc.find_library(ffmpeg_avformat, dirs: ffmpeg_bin_dir),
|
||||||
cc.find_library('avutil-56', dirs: ffmpeg_bin_dir),
|
cc.find_library(ffmpeg_avutil, dirs: ffmpeg_bin_dir),
|
||||||
],
|
],
|
||||||
include_directories: include_directories(ffmpeg_include_dir)
|
include_directories: include_directories(ffmpeg_include_dir)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -140,6 +140,12 @@ By default, scrcpy automatically synchronizes the computer clipboard to the devi
|
|||||||
|
|
||||||
This option disables this automatic synchronization.
|
This option disables this automatic synchronization.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-\-no\-downsize\-on\-error
|
||||||
|
By default, on MediaCodec error, scrcpy automatically tries again with a lower definition.
|
||||||
|
|
||||||
|
This option disables this behavior.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-n, \-\-no\-control
|
.B \-n, \-\-no\-control
|
||||||
Disable device control (mirror the device in read\-only).
|
Disable device control (mirror the device in read\-only).
|
||||||
|
|||||||
@@ -56,14 +56,13 @@ accept_device(libusb_device *device, const char *serial) {
|
|||||||
// devices available on the computer have permission restrictions
|
// devices available on the computer have permission restrictions
|
||||||
|
|
||||||
struct libusb_device_descriptor desc;
|
struct libusb_device_descriptor desc;
|
||||||
libusb_get_device_descriptor(device, &desc);
|
int result = libusb_get_device_descriptor(device, &desc);
|
||||||
|
if (result < 0 || !desc.iSerialNumber) {
|
||||||
if (!desc.iSerialNumber) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
libusb_device_handle *handle;
|
libusb_device_handle *handle;
|
||||||
int result = libusb_open(device, &handle);
|
result = libusb_open(device, &handle);
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -131,31 +130,22 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_cond_init(&aoa->event_cond)) {
|
if (!sc_cond_init(&aoa->event_cond)) {
|
||||||
sc_mutex_destroy(&aoa->mutex);
|
goto error_destroy_mutex;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (libusb_init(&aoa->usb_context) != LIBUSB_SUCCESS) {
|
if (libusb_init(&aoa->usb_context) != LIBUSB_SUCCESS) {
|
||||||
sc_cond_destroy(&aoa->event_cond);
|
goto error_destroy_cond;
|
||||||
sc_mutex_destroy(&aoa->mutex);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
aoa->usb_device = sc_aoa_find_usb_device(serial);
|
aoa->usb_device = sc_aoa_find_usb_device(serial);
|
||||||
if (!aoa->usb_device) {
|
if (!aoa->usb_device) {
|
||||||
LOGW("USB device of serial %s not found", serial);
|
LOGW("USB device of serial %s not found", serial);
|
||||||
libusb_exit(aoa->usb_context);
|
goto error_exit_libusb;
|
||||||
sc_mutex_destroy(&aoa->mutex);
|
|
||||||
sc_cond_destroy(&aoa->event_cond);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sc_aoa_open_usb_handle(aoa->usb_device, &aoa->usb_handle) < 0) {
|
if (sc_aoa_open_usb_handle(aoa->usb_device, &aoa->usb_handle) < 0) {
|
||||||
LOGW("Open USB handle failed");
|
LOGW("Open USB handle failed");
|
||||||
libusb_unref_device(aoa->usb_device);
|
goto error_unref_device;
|
||||||
libusb_exit(aoa->usb_context);
|
|
||||||
sc_cond_destroy(&aoa->event_cond);
|
|
||||||
sc_mutex_destroy(&aoa->mutex);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,6 +153,16 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial,
|
|||||||
aoa->acksync = acksync;
|
aoa->acksync = acksync;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
error_unref_device:
|
||||||
|
libusb_unref_device(aoa->usb_device);
|
||||||
|
error_exit_libusb:
|
||||||
|
libusb_exit(aoa->usb_context);
|
||||||
|
error_destroy_cond:
|
||||||
|
sc_cond_destroy(&aoa->event_cond);
|
||||||
|
error_destroy_mutex:
|
||||||
|
sc_mutex_destroy(&aoa->mutex);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
#define OPT_NO_CLIPBOARD_AUTOSYNC 1032
|
#define OPT_NO_CLIPBOARD_AUTOSYNC 1032
|
||||||
#define OPT_TCPIP 1033
|
#define OPT_TCPIP 1033
|
||||||
#define OPT_RAW_KEY_EVENTS 1034
|
#define OPT_RAW_KEY_EVENTS 1034
|
||||||
|
#define OPT_NO_DOWNSIZE_ON_ERROR 1035
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
char shortopt;
|
char shortopt;
|
||||||
@@ -236,6 +237,13 @@ static const struct sc_option options[] = {
|
|||||||
"is preserved.\n"
|
"is preserved.\n"
|
||||||
"Default is 0 (unlimited).",
|
"Default is 0 (unlimited).",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_NO_DOWNSIZE_ON_ERROR,
|
||||||
|
.longopt = "no-downsize-on-error",
|
||||||
|
.text = "By default, on MediaCodec error, scrcpy automatically tries "
|
||||||
|
"again with a lower definition.\n"
|
||||||
|
"This option disables this behavior.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_NO_CLIPBOARD_AUTOSYNC,
|
.longopt_id = OPT_NO_CLIPBOARD_AUTOSYNC,
|
||||||
.longopt = "no-clipboard-autosync",
|
.longopt = "no-clipboard-autosync",
|
||||||
@@ -1312,12 +1320,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
case 'K':
|
case 'K':
|
||||||
#ifdef HAVE_AOA_HID
|
#ifdef HAVE_AOA_HID
|
||||||
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID;
|
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID;
|
||||||
|
break;
|
||||||
#else
|
#else
|
||||||
LOGE("HID over AOA (-K/--hid-keyboard) is not supported on "
|
LOGE("HID over AOA (-K/--hid-keyboard) is not supported on "
|
||||||
"this platform. It is only available on Linux.");
|
"this platform. It is only available on Linux.");
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
break;
|
|
||||||
case OPT_MAX_FPS:
|
case OPT_MAX_FPS:
|
||||||
if (!parse_max_fps(optarg, &opts->max_fps)) {
|
if (!parse_max_fps(optarg, &opts->max_fps)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1331,12 +1339,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
case 'M':
|
case 'M':
|
||||||
#ifdef HAVE_AOA_HID
|
#ifdef HAVE_AOA_HID
|
||||||
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID;
|
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID;
|
||||||
|
break;
|
||||||
#else
|
#else
|
||||||
LOGE("HID over AOA (-M/--hid-mouse) is not supported on this"
|
LOGE("HID over AOA (-M/--hid-mouse) is not supported on this"
|
||||||
"platform. It is only available on Linux.");
|
"platform. It is only available on Linux.");
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
break;
|
|
||||||
case OPT_LOCK_VIDEO_ORIENTATION:
|
case OPT_LOCK_VIDEO_ORIENTATION:
|
||||||
if (!parse_lock_video_orientation(optarg,
|
if (!parse_lock_video_orientation(optarg,
|
||||||
&opts->lock_video_orientation)) {
|
&opts->lock_video_orientation)) {
|
||||||
@@ -1489,24 +1497,27 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
opts->tcpip = true;
|
opts->tcpip = true;
|
||||||
opts->tcpip_dst = optarg;
|
opts->tcpip_dst = optarg;
|
||||||
break;
|
break;
|
||||||
|
case OPT_NO_DOWNSIZE_ON_ERROR:
|
||||||
|
opts->downsize_on_error = false;
|
||||||
|
break;
|
||||||
case OPT_V4L2_SINK:
|
case OPT_V4L2_SINK:
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
opts->v4l2_device = optarg;
|
opts->v4l2_device = optarg;
|
||||||
|
break;
|
||||||
#else
|
#else
|
||||||
LOGE("V4L2 (--v4l2-sink) is only available on Linux.");
|
LOGE("V4L2 (--v4l2-sink) is only available on Linux.");
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
break;
|
|
||||||
case OPT_V4L2_BUFFER:
|
case OPT_V4L2_BUFFER:
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
if (!parse_buffering_time(optarg, &opts->v4l2_buffer)) {
|
if (!parse_buffering_time(optarg, &opts->v4l2_buffer)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
#else
|
#else
|
||||||
LOGE("V4L2 (--v4l2-buffer) is only available on Linux.");
|
LOGE("V4L2 (--v4l2-buffer) is only available on Linux.");
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
@@ -1534,13 +1545,20 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts->v4l2_device && opts->lock_video_orientation
|
if (opts->v4l2_device) {
|
||||||
== SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
if (opts->lock_video_orientation ==
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
||||||
LOGI("Video orientation is locked for v4l2 sink. "
|
LOGI("Video orientation is locked for v4l2 sink. "
|
||||||
"See --lock-video-orientation.");
|
"See --lock-video-orientation.");
|
||||||
opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
|
opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// V4L2 could not handle size change.
|
||||||
|
// Do not log because downsizing on error is the default behavior,
|
||||||
|
// not an explicit request from the user.
|
||||||
|
opts->downsize_on_error = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (opts->v4l2_buffer && !opts->v4l2_device) {
|
if (opts->v4l2_buffer && !opts->v4l2_device) {
|
||||||
LOGE("V4L2 buffer value without V4L2 sink\n");
|
LOGE("V4L2 buffer value without V4L2 sink\n");
|
||||||
return false;
|
return false;
|
||||||
@@ -1573,15 +1591,24 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!opts->control && opts->turn_screen_off) {
|
if (!opts->control) {
|
||||||
|
if (opts->turn_screen_off) {
|
||||||
LOGE("Could not request to turn screen off if control is disabled");
|
LOGE("Could not request to turn screen off if control is disabled");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (opts->stay_awake) {
|
||||||
if (!opts->control && opts->stay_awake) {
|
|
||||||
LOGE("Could not request to stay awake if control is disabled");
|
LOGE("Could not request to stay awake if control is disabled");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (opts->show_touches) {
|
||||||
|
LOGE("Could not request to show touches if control is disabled");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (opts->power_off_on_close) {
|
||||||
|
LOGE("Could not request power off on close if control is disabled");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,178 +0,0 @@
|
|||||||
#include "file_handler.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "adb.h"
|
|
||||||
#include "util/log.h"
|
|
||||||
#include "util/process_intr.h"
|
|
||||||
|
|
||||||
#define DEFAULT_PUSH_TARGET "/sdcard/Download/"
|
|
||||||
|
|
||||||
static void
|
|
||||||
file_handler_request_destroy(struct file_handler_request *req) {
|
|
||||||
free(req->file);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
file_handler_init(struct file_handler *file_handler, const char *serial,
|
|
||||||
const char *push_target) {
|
|
||||||
assert(serial);
|
|
||||||
|
|
||||||
cbuf_init(&file_handler->queue);
|
|
||||||
|
|
||||||
bool ok = sc_mutex_init(&file_handler->mutex);
|
|
||||||
if (!ok) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = sc_cond_init(&file_handler->event_cond);
|
|
||||||
if (!ok) {
|
|
||||||
sc_mutex_destroy(&file_handler->mutex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = sc_intr_init(&file_handler->intr);
|
|
||||||
if (!ok) {
|
|
||||||
sc_cond_destroy(&file_handler->event_cond);
|
|
||||||
sc_mutex_destroy(&file_handler->mutex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
file_handler->serial = strdup(serial);
|
|
||||||
if (!file_handler->serial) {
|
|
||||||
LOG_OOM();
|
|
||||||
sc_intr_destroy(&file_handler->intr);
|
|
||||||
sc_cond_destroy(&file_handler->event_cond);
|
|
||||||
sc_mutex_destroy(&file_handler->mutex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// lazy initialization
|
|
||||||
file_handler->initialized = false;
|
|
||||||
|
|
||||||
file_handler->stopped = false;
|
|
||||||
|
|
||||||
file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
file_handler_destroy(struct file_handler *file_handler) {
|
|
||||||
sc_cond_destroy(&file_handler->event_cond);
|
|
||||||
sc_mutex_destroy(&file_handler->mutex);
|
|
||||||
sc_intr_destroy(&file_handler->intr);
|
|
||||||
free(file_handler->serial);
|
|
||||||
|
|
||||||
struct file_handler_request req;
|
|
||||||
while (cbuf_take(&file_handler->queue, &req)) {
|
|
||||||
file_handler_request_destroy(&req);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
file_handler_request(struct file_handler *file_handler,
|
|
||||||
file_handler_action_t action, char *file) {
|
|
||||||
// start file_handler if it's used for the first time
|
|
||||||
if (!file_handler->initialized) {
|
|
||||||
if (!file_handler_start(file_handler)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
file_handler->initialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGI("Request to %s %s", action == ACTION_INSTALL_APK ? "install" : "push",
|
|
||||||
file);
|
|
||||||
struct file_handler_request req = {
|
|
||||||
.action = action,
|
|
||||||
.file = file,
|
|
||||||
};
|
|
||||||
|
|
||||||
sc_mutex_lock(&file_handler->mutex);
|
|
||||||
bool was_empty = cbuf_is_empty(&file_handler->queue);
|
|
||||||
bool res = cbuf_push(&file_handler->queue, req);
|
|
||||||
if (was_empty) {
|
|
||||||
sc_cond_signal(&file_handler->event_cond);
|
|
||||||
}
|
|
||||||
sc_mutex_unlock(&file_handler->mutex);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
run_file_handler(void *data) {
|
|
||||||
struct file_handler *file_handler = data;
|
|
||||||
struct sc_intr *intr = &file_handler->intr;
|
|
||||||
|
|
||||||
const char *serial = file_handler->serial;
|
|
||||||
assert(serial);
|
|
||||||
|
|
||||||
const char *push_target = file_handler->push_target;
|
|
||||||
assert(push_target);
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
sc_mutex_lock(&file_handler->mutex);
|
|
||||||
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
|
|
||||||
sc_cond_wait(&file_handler->event_cond, &file_handler->mutex);
|
|
||||||
}
|
|
||||||
if (file_handler->stopped) {
|
|
||||||
// stop immediately, do not process further events
|
|
||||||
sc_mutex_unlock(&file_handler->mutex);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
struct file_handler_request req;
|
|
||||||
bool non_empty = cbuf_take(&file_handler->queue, &req);
|
|
||||||
assert(non_empty);
|
|
||||||
(void) non_empty;
|
|
||||||
sc_mutex_unlock(&file_handler->mutex);
|
|
||||||
|
|
||||||
if (req.action == ACTION_INSTALL_APK) {
|
|
||||||
LOGI("Installing %s...", req.file);
|
|
||||||
bool ok = adb_install(intr, serial, req.file, 0);
|
|
||||||
if (ok) {
|
|
||||||
LOGI("%s successfully installed", req.file);
|
|
||||||
} else {
|
|
||||||
LOGE("Failed to install %s", req.file);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LOGI("Pushing %s...", req.file);
|
|
||||||
bool ok = adb_push(intr, serial, req.file, push_target, 0);
|
|
||||||
if (ok) {
|
|
||||||
LOGI("%s successfully pushed to %s", req.file, push_target);
|
|
||||||
} else {
|
|
||||||
LOGE("Failed to push %s to %s", req.file, push_target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file_handler_request_destroy(&req);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
file_handler_start(struct file_handler *file_handler) {
|
|
||||||
LOGD("Starting file_handler thread");
|
|
||||||
|
|
||||||
bool ok = sc_thread_create(&file_handler->thread, run_file_handler,
|
|
||||||
"scrcpy-file", file_handler);
|
|
||||||
if (!ok) {
|
|
||||||
LOGC("Could not start file_handler thread");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
file_handler_stop(struct file_handler *file_handler) {
|
|
||||||
sc_mutex_lock(&file_handler->mutex);
|
|
||||||
file_handler->stopped = true;
|
|
||||||
sc_cond_signal(&file_handler->event_cond);
|
|
||||||
sc_intr_interrupt(&file_handler->intr);
|
|
||||||
sc_mutex_unlock(&file_handler->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
file_handler_join(struct file_handler *file_handler) {
|
|
||||||
sc_thread_join(&file_handler->thread, NULL);
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
#ifndef FILE_HANDLER_H
|
|
||||||
#define FILE_HANDLER_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include "adb.h"
|
|
||||||
#include "util/cbuf.h"
|
|
||||||
#include "util/thread.h"
|
|
||||||
#include "util/intr.h"
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
ACTION_INSTALL_APK,
|
|
||||||
ACTION_PUSH_FILE,
|
|
||||||
} file_handler_action_t;
|
|
||||||
|
|
||||||
struct file_handler_request {
|
|
||||||
file_handler_action_t action;
|
|
||||||
char *file;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct file_handler_request_queue CBUF(struct file_handler_request, 16);
|
|
||||||
|
|
||||||
struct file_handler {
|
|
||||||
char *serial;
|
|
||||||
const char *push_target;
|
|
||||||
sc_thread thread;
|
|
||||||
sc_mutex mutex;
|
|
||||||
sc_cond event_cond;
|
|
||||||
bool stopped;
|
|
||||||
bool initialized;
|
|
||||||
struct file_handler_request_queue queue;
|
|
||||||
|
|
||||||
struct sc_intr intr;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool
|
|
||||||
file_handler_init(struct file_handler *file_handler, const char *serial,
|
|
||||||
const char *push_target);
|
|
||||||
|
|
||||||
void
|
|
||||||
file_handler_destroy(struct file_handler *file_handler);
|
|
||||||
|
|
||||||
bool
|
|
||||||
file_handler_start(struct file_handler *file_handler);
|
|
||||||
|
|
||||||
void
|
|
||||||
file_handler_stop(struct file_handler *file_handler);
|
|
||||||
|
|
||||||
void
|
|
||||||
file_handler_join(struct file_handler *file_handler);
|
|
||||||
|
|
||||||
// take ownership of file, and will free() it
|
|
||||||
bool
|
|
||||||
file_handler_request(struct file_handler *file_handler,
|
|
||||||
file_handler_action_t action,
|
|
||||||
char *file);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
178
app/src/file_pusher.c
Normal file
178
app/src/file_pusher.c
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
#include "file_pusher.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "adb.h"
|
||||||
|
#include "util/log.h"
|
||||||
|
#include "util/process_intr.h"
|
||||||
|
|
||||||
|
#define DEFAULT_PUSH_TARGET "/sdcard/Download/"
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_file_pusher_request_destroy(struct sc_file_pusher_request *req) {
|
||||||
|
free(req->file);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_file_pusher_init(struct sc_file_pusher *fp, const char *serial,
|
||||||
|
const char *push_target) {
|
||||||
|
assert(serial);
|
||||||
|
|
||||||
|
cbuf_init(&fp->queue);
|
||||||
|
|
||||||
|
bool ok = sc_mutex_init(&fp->mutex);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = sc_cond_init(&fp->event_cond);
|
||||||
|
if (!ok) {
|
||||||
|
sc_mutex_destroy(&fp->mutex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = sc_intr_init(&fp->intr);
|
||||||
|
if (!ok) {
|
||||||
|
sc_cond_destroy(&fp->event_cond);
|
||||||
|
sc_mutex_destroy(&fp->mutex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fp->serial = strdup(serial);
|
||||||
|
if (!fp->serial) {
|
||||||
|
LOG_OOM();
|
||||||
|
sc_intr_destroy(&fp->intr);
|
||||||
|
sc_cond_destroy(&fp->event_cond);
|
||||||
|
sc_mutex_destroy(&fp->mutex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lazy initialization
|
||||||
|
fp->initialized = false;
|
||||||
|
|
||||||
|
fp->stopped = false;
|
||||||
|
|
||||||
|
fp->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_file_pusher_destroy(struct sc_file_pusher *fp) {
|
||||||
|
sc_cond_destroy(&fp->event_cond);
|
||||||
|
sc_mutex_destroy(&fp->mutex);
|
||||||
|
sc_intr_destroy(&fp->intr);
|
||||||
|
free(fp->serial);
|
||||||
|
|
||||||
|
struct sc_file_pusher_request req;
|
||||||
|
while (cbuf_take(&fp->queue, &req)) {
|
||||||
|
sc_file_pusher_request_destroy(&req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_file_pusher_request(struct sc_file_pusher *fp,
|
||||||
|
enum sc_file_pusher_action action, char *file) {
|
||||||
|
// start file_pusher if it's used for the first time
|
||||||
|
if (!fp->initialized) {
|
||||||
|
if (!sc_file_pusher_start(fp)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fp->initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGI("Request to %s %s", action == SC_FILE_PUSHER_ACTION_INSTALL_APK
|
||||||
|
? "install" : "push",
|
||||||
|
file);
|
||||||
|
struct sc_file_pusher_request req = {
|
||||||
|
.action = action,
|
||||||
|
.file = file,
|
||||||
|
};
|
||||||
|
|
||||||
|
sc_mutex_lock(&fp->mutex);
|
||||||
|
bool was_empty = cbuf_is_empty(&fp->queue);
|
||||||
|
bool res = cbuf_push(&fp->queue, req);
|
||||||
|
if (was_empty) {
|
||||||
|
sc_cond_signal(&fp->event_cond);
|
||||||
|
}
|
||||||
|
sc_mutex_unlock(&fp->mutex);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_file_pusher(void *data) {
|
||||||
|
struct sc_file_pusher *fp = data;
|
||||||
|
struct sc_intr *intr = &fp->intr;
|
||||||
|
|
||||||
|
const char *serial = fp->serial;
|
||||||
|
assert(serial);
|
||||||
|
|
||||||
|
const char *push_target = fp->push_target;
|
||||||
|
assert(push_target);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
sc_mutex_lock(&fp->mutex);
|
||||||
|
while (!fp->stopped && cbuf_is_empty(&fp->queue)) {
|
||||||
|
sc_cond_wait(&fp->event_cond, &fp->mutex);
|
||||||
|
}
|
||||||
|
if (fp->stopped) {
|
||||||
|
// stop immediately, do not process further events
|
||||||
|
sc_mutex_unlock(&fp->mutex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
struct sc_file_pusher_request req;
|
||||||
|
bool non_empty = cbuf_take(&fp->queue, &req);
|
||||||
|
assert(non_empty);
|
||||||
|
(void) non_empty;
|
||||||
|
sc_mutex_unlock(&fp->mutex);
|
||||||
|
|
||||||
|
if (req.action == SC_FILE_PUSHER_ACTION_INSTALL_APK) {
|
||||||
|
LOGI("Installing %s...", req.file);
|
||||||
|
bool ok = adb_install(intr, serial, req.file, 0);
|
||||||
|
if (ok) {
|
||||||
|
LOGI("%s successfully installed", req.file);
|
||||||
|
} else {
|
||||||
|
LOGE("Failed to install %s", req.file);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOGI("Pushing %s...", req.file);
|
||||||
|
bool ok = adb_push(intr, serial, req.file, push_target, 0);
|
||||||
|
if (ok) {
|
||||||
|
LOGI("%s successfully pushed to %s", req.file, push_target);
|
||||||
|
} else {
|
||||||
|
LOGE("Failed to push %s to %s", req.file, push_target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_file_pusher_request_destroy(&req);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_file_pusher_start(struct sc_file_pusher *fp) {
|
||||||
|
LOGD("Starting file_pusher thread");
|
||||||
|
|
||||||
|
bool ok = sc_thread_create(&fp->thread, run_file_pusher, "scrcpy-file", fp);
|
||||||
|
if (!ok) {
|
||||||
|
LOGC("Could not start file_pusher thread");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_file_pusher_stop(struct sc_file_pusher *fp) {
|
||||||
|
sc_mutex_lock(&fp->mutex);
|
||||||
|
fp->stopped = true;
|
||||||
|
sc_cond_signal(&fp->event_cond);
|
||||||
|
sc_intr_interrupt(&fp->intr);
|
||||||
|
sc_mutex_unlock(&fp->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_file_pusher_join(struct sc_file_pusher *fp) {
|
||||||
|
sc_thread_join(&fp->thread, NULL);
|
||||||
|
}
|
||||||
59
app/src/file_pusher.h
Normal file
59
app/src/file_pusher.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#ifndef SC_FILE_PUSHER_H
|
||||||
|
#define SC_FILE_PUSHER_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "adb.h"
|
||||||
|
#include "util/cbuf.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
#include "util/intr.h"
|
||||||
|
|
||||||
|
enum sc_file_pusher_action {
|
||||||
|
SC_FILE_PUSHER_ACTION_INSTALL_APK,
|
||||||
|
SC_FILE_PUSHER_ACTION_PUSH_FILE,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_file_pusher_request {
|
||||||
|
enum sc_file_pusher_action action;
|
||||||
|
char *file;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_file_pusher_request_queue CBUF(struct sc_file_pusher_request, 16);
|
||||||
|
|
||||||
|
struct sc_file_pusher {
|
||||||
|
char *serial;
|
||||||
|
const char *push_target;
|
||||||
|
sc_thread thread;
|
||||||
|
sc_mutex mutex;
|
||||||
|
sc_cond event_cond;
|
||||||
|
bool stopped;
|
||||||
|
bool initialized;
|
||||||
|
struct sc_file_pusher_request_queue queue;
|
||||||
|
|
||||||
|
struct sc_intr intr;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_file_pusher_init(struct sc_file_pusher *fp, const char *serial,
|
||||||
|
const char *push_target);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_file_pusher_destroy(struct sc_file_pusher *fp);
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_file_pusher_start(struct sc_file_pusher *fp);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_file_pusher_stop(struct sc_file_pusher *fp);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_file_pusher_join(struct sc_file_pusher *fp);
|
||||||
|
|
||||||
|
// take ownership of file, and will free() it
|
||||||
|
bool
|
||||||
|
sc_file_pusher_request(struct sc_file_pusher *fp,
|
||||||
|
enum sc_file_pusher_action action, char *file);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -262,6 +262,6 @@ void
|
|||||||
sc_hid_mouse_destroy(struct sc_hid_mouse *mouse) {
|
sc_hid_mouse_destroy(struct sc_hid_mouse *mouse) {
|
||||||
bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID);
|
bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGW("Could not unregister HID");
|
LOGW("Could not unregister HID mouse");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef HID_MOUSE_H
|
#ifndef SC_HID_MOUSE_H
|
||||||
#define HID_MOUSE_H
|
#define SC_HID_MOUSE_H
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
|
|||||||
@@ -124,15 +124,15 @@ is_shortcut_mod(struct sc_input_manager *im, uint16_t sdl_mod) {
|
|||||||
void
|
void
|
||||||
sc_input_manager_init(struct sc_input_manager *im,
|
sc_input_manager_init(struct sc_input_manager *im,
|
||||||
const struct sc_input_manager_params *params) {
|
const struct sc_input_manager_params *params) {
|
||||||
assert(!params->control || (params->kp && params->kp->ops));
|
assert(!params->controller || (params->kp && params->kp->ops));
|
||||||
assert(!params->control || (params->mp && params->mp->ops));
|
assert(!params->controller || (params->mp && params->mp->ops));
|
||||||
|
|
||||||
im->controller = params->controller;
|
im->controller = params->controller;
|
||||||
|
im->fp = params->fp;
|
||||||
im->screen = params->screen;
|
im->screen = params->screen;
|
||||||
im->kp = params->kp;
|
im->kp = params->kp;
|
||||||
im->mp = params->mp;
|
im->mp = params->mp;
|
||||||
|
|
||||||
im->control = params->control;
|
|
||||||
im->forward_all_clicks = params->forward_all_clicks;
|
im->forward_all_clicks = params->forward_all_clicks;
|
||||||
im->legacy_paste = params->legacy_paste;
|
im->legacy_paste = params->legacy_paste;
|
||||||
im->clipboard_autosync = params->clipboard_autosync;
|
im->clipboard_autosync = params->clipboard_autosync;
|
||||||
@@ -433,9 +433,7 @@ inverse_point(struct sc_point point, struct sc_size size) {
|
|||||||
static void
|
static void
|
||||||
sc_input_manager_process_key(struct sc_input_manager *im,
|
sc_input_manager_process_key(struct sc_input_manager *im,
|
||||||
const SDL_KeyboardEvent *event) {
|
const SDL_KeyboardEvent *event) {
|
||||||
// control: indicates the state of the command-line option --no-control
|
// controller is NULL if --no-control is requested
|
||||||
bool control = im->control;
|
|
||||||
|
|
||||||
struct sc_controller *controller = im->controller;
|
struct sc_controller *controller = im->controller;
|
||||||
|
|
||||||
SDL_Keycode keycode = event->keysym.sym;
|
SDL_Keycode keycode = event->keysym.sym;
|
||||||
@@ -462,33 +460,33 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
||||||
switch (keycode) {
|
switch (keycode) {
|
||||||
case SDLK_h:
|
case SDLK_h:
|
||||||
if (control && !shift && !repeat) {
|
if (controller && !shift && !repeat) {
|
||||||
action_home(controller, action);
|
action_home(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_b: // fall-through
|
case SDLK_b: // fall-through
|
||||||
case SDLK_BACKSPACE:
|
case SDLK_BACKSPACE:
|
||||||
if (control && !shift && !repeat) {
|
if (controller && !shift && !repeat) {
|
||||||
action_back(controller, action);
|
action_back(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_s:
|
case SDLK_s:
|
||||||
if (control && !shift && !repeat) {
|
if (controller && !shift && !repeat) {
|
||||||
action_app_switch(controller, action);
|
action_app_switch(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_m:
|
case SDLK_m:
|
||||||
if (control && !shift && !repeat) {
|
if (controller && !shift && !repeat) {
|
||||||
action_menu(controller, action);
|
action_menu(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_p:
|
case SDLK_p:
|
||||||
if (control && !shift && !repeat) {
|
if (controller && !shift && !repeat) {
|
||||||
action_power(controller, action);
|
action_power(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_o:
|
case SDLK_o:
|
||||||
if (control && !repeat && down) {
|
if (controller && !repeat && down) {
|
||||||
enum screen_power_mode mode = shift
|
enum screen_power_mode mode = shift
|
||||||
? SCREEN_POWER_MODE_NORMAL
|
? SCREEN_POWER_MODE_NORMAL
|
||||||
: SCREEN_POWER_MODE_OFF;
|
: SCREEN_POWER_MODE_OFF;
|
||||||
@@ -496,13 +494,13 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_DOWN:
|
case SDLK_DOWN:
|
||||||
if (control && !shift) {
|
if (controller && !shift) {
|
||||||
// forward repeated events
|
// forward repeated events
|
||||||
action_volume_down(controller, action);
|
action_volume_down(controller, action);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_UP:
|
case SDLK_UP:
|
||||||
if (control && !shift) {
|
if (controller && !shift) {
|
||||||
// forward repeated events
|
// forward repeated events
|
||||||
action_volume_up(controller, action);
|
action_volume_up(controller, action);
|
||||||
}
|
}
|
||||||
@@ -518,19 +516,19 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_c:
|
case SDLK_c:
|
||||||
if (control && !shift && !repeat && down) {
|
if (controller && !shift && !repeat && down) {
|
||||||
get_device_clipboard(controller,
|
get_device_clipboard(controller,
|
||||||
GET_CLIPBOARD_COPY_KEY_COPY);
|
GET_CLIPBOARD_COPY_KEY_COPY);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_x:
|
case SDLK_x:
|
||||||
if (control && !shift && !repeat && down) {
|
if (controller && !shift && !repeat && down) {
|
||||||
get_device_clipboard(controller,
|
get_device_clipboard(controller,
|
||||||
GET_CLIPBOARD_COPY_KEY_CUT);
|
GET_CLIPBOARD_COPY_KEY_CUT);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_v:
|
case SDLK_v:
|
||||||
if (control && !repeat && down) {
|
if (controller && !repeat && down) {
|
||||||
if (shift || im->legacy_paste) {
|
if (shift || im->legacy_paste) {
|
||||||
// inject the text as input events
|
// inject the text as input events
|
||||||
clipboard_paste(controller);
|
clipboard_paste(controller);
|
||||||
@@ -563,7 +561,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_n:
|
case SDLK_n:
|
||||||
if (control && !repeat && down) {
|
if (controller && !repeat && down) {
|
||||||
if (shift) {
|
if (shift) {
|
||||||
collapse_panels(controller);
|
collapse_panels(controller);
|
||||||
} else if (im->key_repeat == 0) {
|
} else if (im->key_repeat == 0) {
|
||||||
@@ -574,7 +572,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_r:
|
case SDLK_r:
|
||||||
if (control && !shift && !repeat && down) {
|
if (controller && !shift && !repeat && down) {
|
||||||
rotate_device(controller);
|
rotate_device(controller);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -583,7 +581,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!control) {
|
if (!controller) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -700,7 +698,7 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
|
|||||||
static void
|
static void
|
||||||
sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||||
const SDL_MouseButtonEvent *event) {
|
const SDL_MouseButtonEvent *event) {
|
||||||
bool control = im->control;
|
struct sc_controller *controller = im->controller;
|
||||||
|
|
||||||
if (event->which == SDL_TOUCH_MOUSEID) {
|
if (event->which == SDL_TOUCH_MOUSEID) {
|
||||||
// simulated from touch events, so it's a duplicate
|
// simulated from touch events, so it's a duplicate
|
||||||
@@ -709,28 +707,30 @@ sc_input_manager_process_mouse_button(struct sc_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) {
|
||||||
|
if (controller) {
|
||||||
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
||||||
|
|
||||||
if (control && event->button == SDL_BUTTON_X1) {
|
if (event->button == SDL_BUTTON_X1) {
|
||||||
action_app_switch(im->controller, action);
|
action_app_switch(controller, action);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (control && event->button == SDL_BUTTON_X2 && down) {
|
if (event->button == SDL_BUTTON_X2 && down) {
|
||||||
if (event->clicks < 2) {
|
if (event->clicks < 2) {
|
||||||
expand_notification_panel(im->controller);
|
expand_notification_panel(controller);
|
||||||
} else {
|
} else {
|
||||||
expand_settings_panel(im->controller);
|
expand_settings_panel(controller);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (control && event->button == SDL_BUTTON_RIGHT) {
|
if (event->button == SDL_BUTTON_RIGHT) {
|
||||||
press_back_or_turn_screen_on(im->controller, action);
|
press_back_or_turn_screen_on(controller, action);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (control && event->button == SDL_BUTTON_MIDDLE) {
|
if (event->button == SDL_BUTTON_MIDDLE) {
|
||||||
action_home(im->controller, action);
|
action_home(controller, action);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// double-click on black borders resize to fit the device screen
|
// double-click on black borders resize to fit the device screen
|
||||||
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
|
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
|
||||||
@@ -750,7 +750,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
|||||||
// otherwise, send the click event to the device
|
// otherwise, send the click event to the device
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!control) {
|
if (!controller) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -834,45 +834,81 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
|
|||||||
im->mp->ops->process_mouse_scroll(im->mp, &evt);
|
im->mp->ops->process_mouse_scroll(im->mp, &evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
static bool
|
||||||
|
is_apk(const char *file) {
|
||||||
|
const char *ext = strrchr(file, '.');
|
||||||
|
return ext && !strcmp(ext, ".apk");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_input_manager_process_file(struct sc_input_manager *im,
|
||||||
|
const SDL_DropEvent *event) {
|
||||||
|
char *file = strdup(event->file);
|
||||||
|
SDL_free(event->file);
|
||||||
|
if (!file) {
|
||||||
|
LOG_OOM();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum sc_file_pusher_action action;
|
||||||
|
if (is_apk(file)) {
|
||||||
|
action = SC_FILE_PUSHER_ACTION_INSTALL_APK;
|
||||||
|
} else {
|
||||||
|
action = SC_FILE_PUSHER_ACTION_PUSH_FILE;
|
||||||
|
}
|
||||||
|
bool ok = sc_file_pusher_request(im->fp, action, file);
|
||||||
|
if (!ok) {
|
||||||
|
free(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) {
|
sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) {
|
||||||
|
bool control = im->controller;
|
||||||
switch (event->type) {
|
switch (event->type) {
|
||||||
case SDL_TEXTINPUT:
|
case SDL_TEXTINPUT:
|
||||||
if (!im->control) {
|
if (!control) {
|
||||||
return true;
|
break;
|
||||||
}
|
}
|
||||||
sc_input_manager_process_text_input(im, &event->text);
|
sc_input_manager_process_text_input(im, &event->text);
|
||||||
return true;
|
break;
|
||||||
case SDL_KEYDOWN:
|
case SDL_KEYDOWN:
|
||||||
case SDL_KEYUP:
|
case SDL_KEYUP:
|
||||||
// some key events do not interact with the device, so process the
|
// some key events do not interact with the device, so process the
|
||||||
// event even if control is disabled
|
// event even if control is disabled
|
||||||
sc_input_manager_process_key(im, &event->key);
|
sc_input_manager_process_key(im, &event->key);
|
||||||
return true;
|
break;
|
||||||
case SDL_MOUSEMOTION:
|
case SDL_MOUSEMOTION:
|
||||||
if (!im->control) {
|
if (!control) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
sc_input_manager_process_mouse_motion(im, &event->motion);
|
sc_input_manager_process_mouse_motion(im, &event->motion);
|
||||||
return true;
|
break;
|
||||||
case SDL_MOUSEWHEEL:
|
case SDL_MOUSEWHEEL:
|
||||||
if (!im->control) {
|
if (!control) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
sc_input_manager_process_mouse_wheel(im, &event->wheel);
|
sc_input_manager_process_mouse_wheel(im, &event->wheel);
|
||||||
return true;
|
break;
|
||||||
case SDL_MOUSEBUTTONDOWN:
|
case SDL_MOUSEBUTTONDOWN:
|
||||||
case SDL_MOUSEBUTTONUP:
|
case SDL_MOUSEBUTTONUP:
|
||||||
// some mouse events do not interact with the device, so process
|
// some mouse events do not interact with the device, so process
|
||||||
// the event even if control is disabled
|
// the event even if control is disabled
|
||||||
sc_input_manager_process_mouse_button(im, &event->button);
|
sc_input_manager_process_mouse_button(im, &event->button);
|
||||||
return true;
|
break;
|
||||||
case SDL_FINGERMOTION:
|
case SDL_FINGERMOTION:
|
||||||
case SDL_FINGERDOWN:
|
case SDL_FINGERDOWN:
|
||||||
case SDL_FINGERUP:
|
case SDL_FINGERUP:
|
||||||
|
if (!control) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
sc_input_manager_process_touch(im, &event->tfinger);
|
sc_input_manager_process_touch(im, &event->tfinger);
|
||||||
return true;
|
break;
|
||||||
|
case SDL_DROPFILE: {
|
||||||
|
if (!control) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sc_input_manager_process_file(im, &event->drop);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
|
#include "file_pusher.h"
|
||||||
#include "fps_counter.h"
|
#include "fps_counter.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "trait/key_processor.h"
|
#include "trait/key_processor.h"
|
||||||
@@ -15,12 +16,12 @@
|
|||||||
|
|
||||||
struct sc_input_manager {
|
struct sc_input_manager {
|
||||||
struct sc_controller *controller;
|
struct sc_controller *controller;
|
||||||
|
struct sc_file_pusher *fp;
|
||||||
struct sc_screen *screen;
|
struct sc_screen *screen;
|
||||||
|
|
||||||
struct sc_key_processor *kp;
|
struct sc_key_processor *kp;
|
||||||
struct sc_mouse_processor *mp;
|
struct sc_mouse_processor *mp;
|
||||||
|
|
||||||
bool control;
|
|
||||||
bool forward_all_clicks;
|
bool forward_all_clicks;
|
||||||
bool legacy_paste;
|
bool legacy_paste;
|
||||||
bool clipboard_autosync;
|
bool clipboard_autosync;
|
||||||
@@ -44,11 +45,11 @@ struct sc_input_manager {
|
|||||||
|
|
||||||
struct sc_input_manager_params {
|
struct sc_input_manager_params {
|
||||||
struct sc_controller *controller;
|
struct sc_controller *controller;
|
||||||
|
struct sc_file_pusher *fp;
|
||||||
struct sc_screen *screen;
|
struct sc_screen *screen;
|
||||||
struct sc_key_processor *kp;
|
struct sc_key_processor *kp;
|
||||||
struct sc_mouse_processor *mp;
|
struct sc_mouse_processor *mp;
|
||||||
|
|
||||||
bool control;
|
|
||||||
bool forward_all_clicks;
|
bool forward_all_clicks;
|
||||||
bool legacy_paste;
|
bool legacy_paste;
|
||||||
bool clipboard_autosync;
|
bool clipboard_autosync;
|
||||||
@@ -59,7 +60,7 @@ void
|
|||||||
sc_input_manager_init(struct sc_input_manager *im,
|
sc_input_manager_init(struct sc_input_manager *im,
|
||||||
const struct sc_input_manager_params *params);
|
const struct sc_input_manager_params *params);
|
||||||
|
|
||||||
bool
|
void
|
||||||
sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event);
|
sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.legacy_paste = false,
|
.legacy_paste = false,
|
||||||
.power_off_on_close = false,
|
.power_off_on_close = false,
|
||||||
.clipboard_autosync = true,
|
.clipboard_autosync = true,
|
||||||
|
.downsize_on_error = true,
|
||||||
.tcpip = false,
|
.tcpip = false,
|
||||||
.tcpip_dst = NULL,
|
.tcpip_dst = NULL,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ struct scrcpy_options {
|
|||||||
bool legacy_paste;
|
bool legacy_paste;
|
||||||
bool power_off_on_close;
|
bool power_off_on_close;
|
||||||
bool clipboard_autosync;
|
bool clipboard_autosync;
|
||||||
|
bool downsize_on_error;
|
||||||
bool tcpip;
|
bool tcpip;
|
||||||
const char *tcpip_dst;
|
const char *tcpip_dst;
|
||||||
};
|
};
|
||||||
|
|||||||
104
app/src/scrcpy.c
104
app/src/scrcpy.c
@@ -16,7 +16,7 @@
|
|||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "decoder.h"
|
#include "decoder.h"
|
||||||
#include "events.h"
|
#include "events.h"
|
||||||
#include "file_handler.h"
|
#include "file_pusher.h"
|
||||||
#ifdef HAVE_AOA_HID
|
#ifdef HAVE_AOA_HID
|
||||||
# include "hid_keyboard.h"
|
# include "hid_keyboard.h"
|
||||||
# include "hid_mouse.h"
|
# include "hid_mouse.h"
|
||||||
@@ -44,7 +44,7 @@ struct scrcpy {
|
|||||||
struct sc_v4l2_sink v4l2_sink;
|
struct sc_v4l2_sink v4l2_sink;
|
||||||
#endif
|
#endif
|
||||||
struct sc_controller controller;
|
struct sc_controller controller;
|
||||||
struct file_handler file_handler;
|
struct sc_file_pusher file_pusher;
|
||||||
#ifdef HAVE_AOA_HID
|
#ifdef HAVE_AOA_HID
|
||||||
struct sc_aoa aoa;
|
struct sc_aoa aoa;
|
||||||
// sequence/ack helper to synchronize clipboard and Ctrl+v via HID
|
// sequence/ack helper to synchronize clipboard and Ctrl+v via HID
|
||||||
@@ -149,68 +149,18 @@ sdl_configure(bool display, bool disable_screensaver) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
is_apk(const char *file) {
|
event_loop(struct scrcpy *s) {
|
||||||
const char *ext = strrchr(file, '.');
|
|
||||||
return ext && !strcmp(ext, ".apk");
|
|
||||||
}
|
|
||||||
|
|
||||||
enum event_result {
|
|
||||||
EVENT_RESULT_CONTINUE,
|
|
||||||
EVENT_RESULT_STOPPED_BY_USER,
|
|
||||||
EVENT_RESULT_STOPPED_BY_EOS,
|
|
||||||
};
|
|
||||||
|
|
||||||
static enum event_result
|
|
||||||
handle_event(struct scrcpy *s, const struct scrcpy_options *options,
|
|
||||||
SDL_Event *event) {
|
|
||||||
switch (event->type) {
|
|
||||||
case EVENT_STREAM_STOPPED:
|
|
||||||
LOGD("Video stream stopped");
|
|
||||||
return EVENT_RESULT_STOPPED_BY_EOS;
|
|
||||||
case SDL_QUIT:
|
|
||||||
LOGD("User requested to quit");
|
|
||||||
return EVENT_RESULT_STOPPED_BY_USER;
|
|
||||||
case SDL_DROPFILE: {
|
|
||||||
if (!options->control) {
|
|
||||||
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;
|
|
||||||
if (is_apk(file)) {
|
|
||||||
action = ACTION_INSTALL_APK;
|
|
||||||
} else {
|
|
||||||
action = ACTION_PUSH_FILE;
|
|
||||||
}
|
|
||||||
file_handler_request(&s->file_handler, action, file);
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool consumed = sc_screen_handle_event(&s->screen, event);
|
|
||||||
(void) consumed;
|
|
||||||
|
|
||||||
end:
|
|
||||||
return EVENT_RESULT_CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
event_loop(struct scrcpy *s, const struct scrcpy_options *options) {
|
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while (SDL_WaitEvent(&event)) {
|
while (SDL_WaitEvent(&event)) {
|
||||||
enum event_result result = handle_event(s, options, &event);
|
switch (event.type) {
|
||||||
switch (result) {
|
case EVENT_STREAM_STOPPED:
|
||||||
case EVENT_RESULT_STOPPED_BY_USER:
|
|
||||||
return true;
|
|
||||||
case EVENT_RESULT_STOPPED_BY_EOS:
|
|
||||||
LOGW("Device disconnected");
|
LOGW("Device disconnected");
|
||||||
return false;
|
return false;
|
||||||
case EVENT_RESULT_CONTINUE:
|
case SDL_QUIT:
|
||||||
|
LOGD("User requested to quit");
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
sc_screen_handle_event(&s->screen, &event);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,7 +277,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
bool server_started = false;
|
bool server_started = false;
|
||||||
bool file_handler_initialized = false;
|
bool file_pusher_initialized = false;
|
||||||
bool recorder_initialized = false;
|
bool recorder_initialized = false;
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
bool v4l2_sink_initialized = false;
|
bool v4l2_sink_initialized = false;
|
||||||
@@ -364,6 +314,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.force_adb_forward = options->force_adb_forward,
|
.force_adb_forward = options->force_adb_forward,
|
||||||
.power_off_on_close = options->power_off_on_close,
|
.power_off_on_close = options->power_off_on_close,
|
||||||
.clipboard_autosync = options->clipboard_autosync,
|
.clipboard_autosync = options->clipboard_autosync,
|
||||||
|
.downsize_on_error = options->downsize_on_error,
|
||||||
.tcpip = options->tcpip,
|
.tcpip = options->tcpip,
|
||||||
.tcpip_dst = options->tcpip_dst,
|
.tcpip_dst = options->tcpip_dst,
|
||||||
};
|
};
|
||||||
@@ -406,12 +357,15 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
const char *serial = s->server.params.serial;
|
const char *serial = s->server.params.serial;
|
||||||
assert(serial);
|
assert(serial);
|
||||||
|
|
||||||
|
struct sc_file_pusher *fp = NULL;
|
||||||
|
|
||||||
if (options->display && options->control) {
|
if (options->display && options->control) {
|
||||||
if (!file_handler_init(&s->file_handler, serial,
|
if (!sc_file_pusher_init(&s->file_pusher, serial,
|
||||||
options->push_target)) {
|
options->push_target)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
file_handler_initialized = true;
|
fp = &s->file_pusher;
|
||||||
|
file_pusher_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct decoder *dec = NULL;
|
struct decoder *dec = NULL;
|
||||||
@@ -451,6 +405,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
stream_add_sink(&s->stream, &rec->packet_sink);
|
stream_add_sink(&s->stream, &rec->packet_sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct sc_controller *controller = NULL;
|
||||||
struct sc_key_processor *kp = NULL;
|
struct sc_key_processor *kp = NULL;
|
||||||
struct sc_mouse_processor *mp = NULL;
|
struct sc_mouse_processor *mp = NULL;
|
||||||
|
|
||||||
@@ -556,6 +511,7 @@ aoa_hid_end:
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
controller_started = true;
|
controller_started = true;
|
||||||
|
controller = &s->controller;
|
||||||
|
|
||||||
if (options->turn_screen_off) {
|
if (options->turn_screen_off) {
|
||||||
struct sc_control_msg msg;
|
struct sc_control_msg msg;
|
||||||
@@ -569,15 +525,18 @@ aoa_hid_end:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There is a controller if and only if control is enabled
|
||||||
|
assert(options->control == !!controller);
|
||||||
|
|
||||||
if (options->display) {
|
if (options->display) {
|
||||||
const char *window_title =
|
const char *window_title =
|
||||||
options->window_title ? options->window_title : info->device_name;
|
options->window_title ? options->window_title : info->device_name;
|
||||||
|
|
||||||
struct sc_screen_params screen_params = {
|
struct sc_screen_params screen_params = {
|
||||||
.controller = &s->controller,
|
.controller = controller,
|
||||||
|
.fp = fp,
|
||||||
.kp = kp,
|
.kp = kp,
|
||||||
.mp = mp,
|
.mp = mp,
|
||||||
.control = options->control,
|
|
||||||
.forward_all_clicks = options->forward_all_clicks,
|
.forward_all_clicks = options->forward_all_clicks,
|
||||||
.legacy_paste = options->legacy_paste,
|
.legacy_paste = options->legacy_paste,
|
||||||
.clipboard_autosync = options->clipboard_autosync,
|
.clipboard_autosync = options->clipboard_autosync,
|
||||||
@@ -624,7 +583,7 @@ aoa_hid_end:
|
|||||||
}
|
}
|
||||||
stream_started = true;
|
stream_started = true;
|
||||||
|
|
||||||
ret = event_loop(s, options);
|
ret = event_loop(s);
|
||||||
LOGD("quit...");
|
LOGD("quit...");
|
||||||
|
|
||||||
// Close the window immediately on closing, because screen_destroy() may
|
// Close the window immediately on closing, because screen_destroy() may
|
||||||
@@ -639,6 +598,9 @@ end:
|
|||||||
if (hid_keyboard_initialized) {
|
if (hid_keyboard_initialized) {
|
||||||
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
||||||
}
|
}
|
||||||
|
if (hid_mouse_initialized) {
|
||||||
|
sc_hid_mouse_destroy(&s->mouse_hid);
|
||||||
|
}
|
||||||
sc_aoa_stop(&s->aoa);
|
sc_aoa_stop(&s->aoa);
|
||||||
}
|
}
|
||||||
if (acksync) {
|
if (acksync) {
|
||||||
@@ -648,8 +610,8 @@ end:
|
|||||||
if (controller_started) {
|
if (controller_started) {
|
||||||
sc_controller_stop(&s->controller);
|
sc_controller_stop(&s->controller);
|
||||||
}
|
}
|
||||||
if (file_handler_initialized) {
|
if (file_pusher_initialized) {
|
||||||
file_handler_stop(&s->file_handler);
|
sc_file_pusher_stop(&s->file_pusher);
|
||||||
}
|
}
|
||||||
if (screen_initialized) {
|
if (screen_initialized) {
|
||||||
sc_screen_interrupt(&s->screen);
|
sc_screen_interrupt(&s->screen);
|
||||||
@@ -697,9 +659,9 @@ end:
|
|||||||
recorder_destroy(&s->recorder);
|
recorder_destroy(&s->recorder);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file_handler_initialized) {
|
if (file_pusher_initialized) {
|
||||||
file_handler_join(&s->file_handler);
|
sc_file_pusher_join(&s->file_pusher);
|
||||||
file_handler_destroy(&s->file_handler);
|
sc_file_pusher_destroy(&s->file_pusher);
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_server_destroy(&s->server);
|
sc_server_destroy(&s->server);
|
||||||
|
|||||||
123
app/src/screen.c
123
app/src/screen.c
@@ -156,7 +156,13 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
|
|||||||
return window_size;
|
return window_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline bool
|
||||||
|
sc_screen_is_relative_mode(struct sc_screen *screen) {
|
||||||
|
// screen->im.mp may be NULL if --no-control
|
||||||
|
return screen->im.mp && screen->im.mp->relative_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
sc_screen_capture_mouse(struct sc_screen *screen, bool capture) {
|
sc_screen_capture_mouse(struct sc_screen *screen, bool capture) {
|
||||||
if (SDL_SetRelativeMouseMode(capture)) {
|
if (SDL_SetRelativeMouseMode(capture)) {
|
||||||
LOGE("Could not set relative mouse mode to %s: %s",
|
LOGE("Could not set relative mouse mode to %s: %s",
|
||||||
@@ -369,6 +375,12 @@ sc_screen_init(struct sc_screen *screen,
|
|||||||
screen->mouse_captured = false;
|
screen->mouse_captured = false;
|
||||||
screen->mouse_capture_key_pressed = 0;
|
screen->mouse_capture_key_pressed = 0;
|
||||||
|
|
||||||
|
screen->req.x = params->window_x;
|
||||||
|
screen->req.y = params->window_y;
|
||||||
|
screen->req.width = params->window_width;
|
||||||
|
screen->req.height = params->window_height;
|
||||||
|
screen->req.fullscreen = params->fullscreen;
|
||||||
|
|
||||||
static const struct sc_video_buffer_callbacks cbs = {
|
static const struct sc_video_buffer_callbacks cbs = {
|
||||||
.on_new_frame = sc_video_buffer_on_new_frame,
|
.on_new_frame = sc_video_buffer_on_new_frame,
|
||||||
};
|
};
|
||||||
@@ -397,9 +409,6 @@ sc_screen_init(struct sc_screen *screen,
|
|||||||
get_rotated_size(screen->frame_size, screen->rotation);
|
get_rotated_size(screen->frame_size, screen->rotation);
|
||||||
screen->content_size = content_size;
|
screen->content_size = content_size;
|
||||||
|
|
||||||
struct sc_size window_size =
|
|
||||||
get_initial_optimal_size(content_size,params->window_width,
|
|
||||||
params->window_height);
|
|
||||||
uint32_t window_flags = SDL_WINDOW_HIDDEN
|
uint32_t window_flags = SDL_WINDOW_HIDDEN
|
||||||
| SDL_WINDOW_RESIZABLE
|
| SDL_WINDOW_RESIZABLE
|
||||||
| SDL_WINDOW_ALLOW_HIGHDPI;
|
| SDL_WINDOW_ALLOW_HIGHDPI;
|
||||||
@@ -410,13 +419,9 @@ sc_screen_init(struct sc_screen *screen,
|
|||||||
window_flags |= SDL_WINDOW_BORDERLESS;
|
window_flags |= SDL_WINDOW_BORDERLESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED
|
// The window will be positioned and sized on first video frame
|
||||||
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED;
|
screen->window =
|
||||||
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED
|
SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags);
|
||||||
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED;
|
|
||||||
screen->window = SDL_CreateWindow(params->window_title, x, y,
|
|
||||||
window_size.width, window_size.height,
|
|
||||||
window_flags);
|
|
||||||
if (!screen->window) {
|
if (!screen->window) {
|
||||||
LOGC("Could not create window: %s", SDL_GetError());
|
LOGC("Could not create window: %s", SDL_GetError());
|
||||||
goto error_destroy_fps_counter;
|
goto error_destroy_fps_counter;
|
||||||
@@ -486,10 +491,10 @@ sc_screen_init(struct sc_screen *screen,
|
|||||||
|
|
||||||
struct sc_input_manager_params im_params = {
|
struct sc_input_manager_params im_params = {
|
||||||
.controller = params->controller,
|
.controller = params->controller,
|
||||||
|
.fp = params->fp,
|
||||||
.screen = screen,
|
.screen = screen,
|
||||||
.kp = params->kp,
|
.kp = params->kp,
|
||||||
.mp = params->mp,
|
.mp = params->mp,
|
||||||
.control = params->control,
|
|
||||||
.forward_all_clicks = params->forward_all_clicks,
|
.forward_all_clicks = params->forward_all_clicks,
|
||||||
.legacy_paste = params->legacy_paste,
|
.legacy_paste = params->legacy_paste,
|
||||||
.clipboard_autosync = params->clipboard_autosync,
|
.clipboard_autosync = params->clipboard_autosync,
|
||||||
@@ -498,17 +503,6 @@ sc_screen_init(struct sc_screen *screen,
|
|||||||
|
|
||||||
sc_input_manager_init(&screen->im, &im_params);
|
sc_input_manager_init(&screen->im, &im_params);
|
||||||
|
|
||||||
// Reset the window size to trigger a SIZE_CHANGED event, to workaround
|
|
||||||
// HiDPI issues with some SDL renderers when several displays having
|
|
||||||
// different HiDPI scaling are connected
|
|
||||||
SDL_SetWindowSize(screen->window, window_size.width, window_size.height);
|
|
||||||
|
|
||||||
sc_screen_update_content_rect(screen);
|
|
||||||
|
|
||||||
if (params->fullscreen) {
|
|
||||||
sc_screen_switch_fullscreen(screen);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
||||||
SDL_AddEventWatch(event_watcher, screen);
|
SDL_AddEventWatch(event_watcher, screen);
|
||||||
#endif
|
#endif
|
||||||
@@ -545,7 +539,23 @@ error_destroy_video_buffer:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_screen_show_window(struct sc_screen *screen) {
|
sc_screen_show_initial_window(struct sc_screen *screen) {
|
||||||
|
int x = screen->req.x != SC_WINDOW_POSITION_UNDEFINED
|
||||||
|
? screen->req.x : (int) SDL_WINDOWPOS_CENTERED;
|
||||||
|
int y = screen->req.y != SC_WINDOW_POSITION_UNDEFINED
|
||||||
|
? screen->req.y : (int) SDL_WINDOWPOS_CENTERED;
|
||||||
|
|
||||||
|
struct sc_size window_size =
|
||||||
|
get_initial_optimal_size(screen->content_size, screen->req.width,
|
||||||
|
screen->req.height);
|
||||||
|
|
||||||
|
set_window_size(screen, window_size);
|
||||||
|
SDL_SetWindowPosition(screen->window, x, y);
|
||||||
|
|
||||||
|
if (screen->req.fullscreen) {
|
||||||
|
sc_screen_switch_fullscreen(screen);
|
||||||
|
}
|
||||||
|
|
||||||
SDL_ShowWindow(screen->window);
|
SDL_ShowWindow(screen->window);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -693,6 +703,17 @@ sc_screen_update_frame(struct sc_screen *screen) {
|
|||||||
}
|
}
|
||||||
update_texture(screen, frame);
|
update_texture(screen, frame);
|
||||||
|
|
||||||
|
if (!screen->has_frame) {
|
||||||
|
screen->has_frame = true;
|
||||||
|
// this is the very first frame, show the window
|
||||||
|
sc_screen_show_initial_window(screen);
|
||||||
|
|
||||||
|
if (sc_screen_is_relative_mode(screen)) {
|
||||||
|
// Capture mouse on start
|
||||||
|
sc_screen_capture_mouse(screen, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sc_screen_render(screen, false);
|
sc_screen_render(screen, false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -760,24 +781,22 @@ sc_screen_is_mouse_capture_key(SDL_Keycode key) {
|
|||||||
return key == SDLK_LALT || key == SDLK_LGUI || key == SDLK_RGUI;
|
return key == SDLK_LALT || key == SDLK_LGUI || key == SDLK_RGUI;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
void
|
||||||
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
|
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
|
||||||
|
bool relative_mode = sc_screen_is_relative_mode(screen);
|
||||||
|
|
||||||
switch (event->type) {
|
switch (event->type) {
|
||||||
case EVENT_NEW_FRAME:
|
case EVENT_NEW_FRAME: {
|
||||||
if (!screen->has_frame) {
|
|
||||||
screen->has_frame = true;
|
|
||||||
// this is the very first frame, show the window
|
|
||||||
sc_screen_show_window(screen);
|
|
||||||
}
|
|
||||||
bool ok = sc_screen_update_frame(screen);
|
bool ok = sc_screen_update_frame(screen);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGW("Frame update failed\n");
|
LOGW("Frame update failed\n");
|
||||||
}
|
}
|
||||||
return true;
|
return;
|
||||||
|
}
|
||||||
case SDL_WINDOWEVENT:
|
case SDL_WINDOWEVENT:
|
||||||
if (!screen->has_frame) {
|
if (!screen->has_frame) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
switch (event->window.event) {
|
switch (event->window.event) {
|
||||||
case SDL_WINDOWEVENT_EXPOSED:
|
case SDL_WINDOWEVENT_EXPOSED:
|
||||||
@@ -803,70 +822,72 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
|
|||||||
sc_screen_render(screen, true);
|
sc_screen_render(screen, true);
|
||||||
break;
|
break;
|
||||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||||
if (screen->im.mp->relative_mode) {
|
if (relative_mode) {
|
||||||
sc_screen_capture_mouse(screen, false);
|
sc_screen_capture_mouse(screen, false);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return true;
|
return;
|
||||||
case SDL_KEYDOWN:
|
case SDL_KEYDOWN:
|
||||||
if (screen->im.mp->relative_mode) {
|
if (relative_mode) {
|
||||||
SDL_Keycode key = event->key.keysym.sym;
|
SDL_Keycode key = event->key.keysym.sym;
|
||||||
if (sc_screen_is_mouse_capture_key(key)) {
|
if (sc_screen_is_mouse_capture_key(key)) {
|
||||||
if (!screen->mouse_capture_key_pressed) {
|
if (!screen->mouse_capture_key_pressed) {
|
||||||
screen->mouse_capture_key_pressed = key;
|
screen->mouse_capture_key_pressed = key;
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
// Another mouse capture key has been pressed, cancel
|
// Another mouse capture key has been pressed, cancel
|
||||||
// mouse (un)capture
|
// mouse (un)capture
|
||||||
screen->mouse_capture_key_pressed = 0;
|
screen->mouse_capture_key_pressed = 0;
|
||||||
// Do not return, the event must be forwarded to the
|
|
||||||
// input manager
|
|
||||||
}
|
}
|
||||||
|
// Mouse capture keys are never forwarded to the device
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SDL_KEYUP:
|
case SDL_KEYUP:
|
||||||
if (screen->im.mp->relative_mode) {
|
if (relative_mode) {
|
||||||
SDL_Keycode key = event->key.keysym.sym;
|
SDL_Keycode key = event->key.keysym.sym;
|
||||||
SDL_Keycode cap = screen->mouse_capture_key_pressed;
|
SDL_Keycode cap = screen->mouse_capture_key_pressed;
|
||||||
screen->mouse_capture_key_pressed = 0;
|
screen->mouse_capture_key_pressed = 0;
|
||||||
|
if (sc_screen_is_mouse_capture_key(key)) {
|
||||||
if (key == cap) {
|
if (key == cap) {
|
||||||
// A mouse capture key has been pressed then released:
|
// A mouse capture key has been pressed then released:
|
||||||
// toggle the capture mouse mode
|
// toggle the capture mouse mode
|
||||||
sc_screen_capture_mouse(screen, !screen->mouse_captured);
|
sc_screen_capture_mouse(screen,
|
||||||
return true;
|
!screen->mouse_captured);
|
||||||
|
}
|
||||||
|
// Mouse capture keys are never forwarded to the device
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// Do not return, the event must be forwarded to the input
|
|
||||||
// manager
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SDL_MOUSEWHEEL:
|
case SDL_MOUSEWHEEL:
|
||||||
case SDL_MOUSEMOTION:
|
case SDL_MOUSEMOTION:
|
||||||
case SDL_MOUSEBUTTONDOWN:
|
case SDL_MOUSEBUTTONDOWN:
|
||||||
if (screen->im.mp->relative_mode && !screen->mouse_captured) {
|
if (relative_mode && !screen->mouse_captured) {
|
||||||
// Do not forward to input manager, the mouse will be captured
|
// Do not forward to input manager, the mouse will be captured
|
||||||
// on SDL_MOUSEBUTTONUP
|
// on SDL_MOUSEBUTTONUP
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SDL_FINGERMOTION:
|
case SDL_FINGERMOTION:
|
||||||
case SDL_FINGERDOWN:
|
case SDL_FINGERDOWN:
|
||||||
case SDL_FINGERUP:
|
case SDL_FINGERUP:
|
||||||
if (screen->im.mp->relative_mode) {
|
if (relative_mode) {
|
||||||
// Touch events are not compatible with relative mode
|
// Touch events are not compatible with relative mode
|
||||||
// (coordinates are not relative)
|
// (coordinates are not relative)
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SDL_MOUSEBUTTONUP:
|
case SDL_MOUSEBUTTONUP:
|
||||||
if (screen->im.mp->relative_mode && !screen->mouse_captured) {
|
if (relative_mode && !screen->mouse_captured) {
|
||||||
sc_screen_capture_mouse(screen, true);
|
sc_screen_capture_mouse(screen, true);
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sc_input_manager_handle_event(&screen->im, event);
|
sc_input_manager_handle_event(&screen->im, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_point
|
struct sc_point
|
||||||
|
|||||||
@@ -28,6 +28,15 @@ struct sc_screen {
|
|||||||
struct sc_video_buffer vb;
|
struct sc_video_buffer vb;
|
||||||
struct fps_counter fps_counter;
|
struct fps_counter fps_counter;
|
||||||
|
|
||||||
|
// The initial requested window properties
|
||||||
|
struct {
|
||||||
|
int16_t x;
|
||||||
|
int16_t y;
|
||||||
|
uint16_t width;
|
||||||
|
uint16_t height;
|
||||||
|
bool fullscreen;
|
||||||
|
} req;
|
||||||
|
|
||||||
SDL_Window *window;
|
SDL_Window *window;
|
||||||
SDL_Renderer *renderer;
|
SDL_Renderer *renderer;
|
||||||
SDL_Texture *texture;
|
SDL_Texture *texture;
|
||||||
@@ -61,10 +70,10 @@ struct sc_screen {
|
|||||||
|
|
||||||
struct sc_screen_params {
|
struct sc_screen_params {
|
||||||
struct sc_controller *controller;
|
struct sc_controller *controller;
|
||||||
|
struct sc_file_pusher *fp;
|
||||||
struct sc_key_processor *kp;
|
struct sc_key_processor *kp;
|
||||||
struct sc_mouse_processor *mp;
|
struct sc_mouse_processor *mp;
|
||||||
|
|
||||||
bool control;
|
|
||||||
bool forward_all_clicks;
|
bool forward_all_clicks;
|
||||||
bool legacy_paste;
|
bool legacy_paste;
|
||||||
bool clipboard_autosync;
|
bool clipboard_autosync;
|
||||||
@@ -74,10 +83,10 @@ struct sc_screen_params {
|
|||||||
struct sc_size frame_size;
|
struct sc_size frame_size;
|
||||||
bool always_on_top;
|
bool always_on_top;
|
||||||
|
|
||||||
int16_t window_x;
|
int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||||
int16_t window_y;
|
int16_t window_y; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||||
uint16_t window_width; // accepts SC_WINDOW_POSITION_UNDEFINED
|
uint16_t window_width;
|
||||||
uint16_t window_height; // accepts SC_WINDOW_POSITION_UNDEFINED
|
uint16_t window_height;
|
||||||
|
|
||||||
bool window_borderless;
|
bool window_borderless;
|
||||||
|
|
||||||
@@ -130,7 +139,7 @@ void
|
|||||||
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation);
|
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation);
|
||||||
|
|
||||||
// react to SDL events
|
// react to SDL events
|
||||||
bool
|
void
|
||||||
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event);
|
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event);
|
||||||
|
|
||||||
// convert point from window coordinates to frame coordinates
|
// convert point from window coordinates to frame coordinates
|
||||||
|
|||||||
@@ -234,6 +234,10 @@ execute_server(struct sc_server *server,
|
|||||||
// By default, clipboard_autosync is true
|
// By default, clipboard_autosync is true
|
||||||
ADD_PARAM("clipboard_autosync=false");
|
ADD_PARAM("clipboard_autosync=false");
|
||||||
}
|
}
|
||||||
|
if (!params->downsize_on_error) {
|
||||||
|
// By default, downsize_on_error is true
|
||||||
|
ADD_PARAM("downsize_on_error=false");
|
||||||
|
}
|
||||||
|
|
||||||
#undef ADD_PARAM
|
#undef ADD_PARAM
|
||||||
#undef STRBOOL
|
#undef STRBOOL
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ struct sc_server_params {
|
|||||||
bool force_adb_forward;
|
bool force_adb_forward;
|
||||||
bool power_off_on_close;
|
bool power_off_on_close;
|
||||||
bool clipboard_autosync;
|
bool clipboard_autosync;
|
||||||
|
bool downsize_on_error;
|
||||||
bool tcpip;
|
bool tcpip;
|
||||||
const char *tcpip_dst;
|
const char *tcpip_dst;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ cpu = 'i686'
|
|||||||
endian = 'little'
|
endian = 'little'
|
||||||
|
|
||||||
[properties]
|
[properties]
|
||||||
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win32-shared'
|
ffmpeg_avcodec = 'avcodec-58'
|
||||||
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win32-dev'
|
ffmpeg_avformat = 'avformat-58'
|
||||||
|
ffmpeg_avutil = 'avutil-56'
|
||||||
|
prebuilt_ffmpeg = 'ffmpeg-4.3.1-win32-shared'
|
||||||
prebuilt_sdl2 = 'SDL2-2.0.18/i686-w64-mingw32'
|
prebuilt_sdl2 = 'SDL2-2.0.18/i686-w64-mingw32'
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ cpu = 'x86_64'
|
|||||||
endian = 'little'
|
endian = 'little'
|
||||||
|
|
||||||
[properties]
|
[properties]
|
||||||
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win64-shared'
|
ffmpeg_avcodec = 'avcodec-59'
|
||||||
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win64-dev'
|
ffmpeg_avformat = 'avformat-59'
|
||||||
|
ffmpeg_avutil = 'avutil-57'
|
||||||
|
prebuilt_ffmpeg = 'ffmpeg-5.0-full_build-shared'
|
||||||
prebuilt_sdl2 = 'SDL2-2.0.18/x86_64-w64-mingw32'
|
prebuilt_sdl2 = 'SDL2-2.0.18/x86_64-w64-mingw32'
|
||||||
|
|||||||
@@ -1,33 +1,26 @@
|
|||||||
.PHONY: prepare-win32 prepare-win64 \
|
.PHONY: prepare-win32 prepare-win64 \
|
||||||
prepare-ffmpeg-shared-win32 \
|
prepare-ffmpeg-win32 \
|
||||||
prepare-ffmpeg-dev-win32 \
|
prepare-ffmpeg-win64 \
|
||||||
prepare-ffmpeg-shared-win64 \
|
|
||||||
prepare-ffmpeg-dev-win64 \
|
|
||||||
prepare-sdl2 \
|
prepare-sdl2 \
|
||||||
prepare-adb
|
prepare-adb
|
||||||
|
|
||||||
prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32 prepare-adb
|
prepare-win32: prepare-sdl2 prepare-ffmpeg-win32 prepare-adb
|
||||||
prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb
|
prepare-win64: prepare-sdl2 prepare-ffmpeg-win64 prepare-adb
|
||||||
|
|
||||||
prepare-ffmpeg-shared-win32:
|
# Use old FFmpeg version for win32, there are no new prebuilts
|
||||||
|
prepare-ffmpeg-win32:
|
||||||
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-shared.zip \
|
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-shared.zip \
|
||||||
357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2 \
|
357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2 \
|
||||||
ffmpeg-4.3.1-win32-shared
|
ffmpeg-4.3.1-win32-shared
|
||||||
|
|
||||||
prepare-ffmpeg-dev-win32:
|
|
||||||
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-dev.zip \
|
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-dev.zip \
|
||||||
230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b \
|
230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b \
|
||||||
ffmpeg-4.3.1-win32-dev
|
ffmpeg-4.3.1-win32-dev
|
||||||
|
ln -sf ../ffmpeg-4.3.1-win32-dev/include ffmpeg-4.3.1-win32-shared/
|
||||||
|
|
||||||
prepare-ffmpeg-shared-win64:
|
prepare-ffmpeg-win64:
|
||||||
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-shared.zip \
|
@./prepare-dep https://github.com/GyanD/codexffmpeg/releases/download/5.0/ffmpeg-5.0-full_build-shared.7z \
|
||||||
dd29b7f92f48dead4dd940492c7509138c0f99db445076d0a597007298a79940 \
|
e5900f6cecd4c438d398bd2fc308736c10b857cd8dd61c11bcfb05bff5d1211a \
|
||||||
ffmpeg-4.3.1-win64-shared
|
ffmpeg-5.0-full_build-shared
|
||||||
|
|
||||||
prepare-ffmpeg-dev-win64:
|
|
||||||
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-dev.zip \
|
|
||||||
2e8038242cf8e1bd095c2978f196ff0462b122cc6ef7e74626a6af15459d8b81 \
|
|
||||||
ffmpeg-4.3.1-win64-dev
|
|
||||||
|
|
||||||
prepare-sdl2:
|
prepare-sdl2:
|
||||||
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.18-mingw.tar.gz \
|
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.18-mingw.tar.gz \
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ extract() {
|
|||||||
elif [[ "$file" == *.tar.gz ]]
|
elif [[ "$file" == *.tar.gz ]]
|
||||||
then
|
then
|
||||||
tar xf "$file"
|
tar xf "$file"
|
||||||
|
elif [[ "$file" == *.7z ]]
|
||||||
|
then
|
||||||
|
7z x "$file"
|
||||||
else
|
else
|
||||||
echo "Unsupported file: $file"
|
echo "Unsupported file: $file"
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
10
release.mk
10
release.mk
@@ -110,11 +110,11 @@ dist-win64: build-server build-win64
|
|||||||
cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)"
|
cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)"
|
||||||
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
|
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
|
||||||
cp data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)"
|
cp data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)"
|
||||||
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/swscale-5.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
|
|||||||
@@ -46,15 +46,17 @@ public final class DesktopConnection implements Closeable {
|
|||||||
return localSocket;
|
return localSocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DesktopConnection open(Device device, boolean tunnelForward, boolean control) throws IOException {
|
public static DesktopConnection open(boolean tunnelForward, boolean control, boolean sendDummyByte) throws IOException {
|
||||||
LocalSocket videoSocket;
|
LocalSocket videoSocket;
|
||||||
LocalSocket controlSocket = null;
|
LocalSocket controlSocket = null;
|
||||||
if (tunnelForward) {
|
if (tunnelForward) {
|
||||||
LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME);
|
LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME);
|
||||||
try {
|
try {
|
||||||
videoSocket = localServerSocket.accept();
|
videoSocket = localServerSocket.accept();
|
||||||
|
if (sendDummyByte) {
|
||||||
// send one byte so the client may read() to detect a connection error
|
// send one byte so the client may read() to detect a connection error
|
||||||
videoSocket.getOutputStream().write(0);
|
videoSocket.getOutputStream().write(0);
|
||||||
|
}
|
||||||
if (control) {
|
if (control) {
|
||||||
try {
|
try {
|
||||||
controlSocket = localServerSocket.accept();
|
controlSocket = localServerSocket.accept();
|
||||||
@@ -78,10 +80,7 @@ public final class DesktopConnection implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DesktopConnection connection = new DesktopConnection(videoSocket, controlSocket);
|
return new DesktopConnection(videoSocket, controlSocket);
|
||||||
Size videoSize = device.getScreenInfo().getVideoSize();
|
|
||||||
connection.send(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
|
|
||||||
return connection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
@@ -95,7 +94,7 @@ public final class DesktopConnection implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void send(String deviceName, int width, int height) throws IOException {
|
public void sendDeviceMeta(String deviceName, int width, int height) throws IOException {
|
||||||
byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4];
|
byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4];
|
||||||
|
|
||||||
byte[] deviceNameBytes = deviceName.getBytes(StandardCharsets.UTF_8);
|
byte[] deviceNameBytes = deviceName.getBytes(StandardCharsets.UTF_8);
|
||||||
|
|||||||
@@ -42,6 +42,11 @@ public final class Device {
|
|||||||
void onClipboardTextChanged(String text);
|
void onClipboardTextChanged(String text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final Size deviceSize;
|
||||||
|
private final Rect crop;
|
||||||
|
private int maxSize;
|
||||||
|
private final int lockVideoOrientation;
|
||||||
|
|
||||||
private ScreenInfo screenInfo;
|
private ScreenInfo screenInfo;
|
||||||
private RotationListener rotationListener;
|
private RotationListener rotationListener;
|
||||||
private ClipboardListener clipboardListener;
|
private ClipboardListener clipboardListener;
|
||||||
@@ -69,7 +74,12 @@ public final class Device {
|
|||||||
|
|
||||||
int displayInfoFlags = displayInfo.getFlags();
|
int displayInfoFlags = displayInfo.getFlags();
|
||||||
|
|
||||||
screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockVideoOrientation());
|
deviceSize = displayInfo.getSize();
|
||||||
|
crop = options.getCrop();
|
||||||
|
maxSize = options.getMaxSize();
|
||||||
|
lockVideoOrientation = options.getLockVideoOrientation();
|
||||||
|
|
||||||
|
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
|
||||||
layerStack = displayInfo.getLayerStack();
|
layerStack = displayInfo.getLayerStack();
|
||||||
|
|
||||||
SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
|
SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
|
||||||
@@ -123,6 +133,11 @@ public final class Device {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void setMaxSize(int newMaxSize) {
|
||||||
|
maxSize = newMaxSize;
|
||||||
|
screenInfo = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation);
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized ScreenInfo getScreenInfo() {
|
public synchronized ScreenInfo getScreenInfo() {
|
||||||
return screenInfo;
|
return screenInfo;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ public class Options {
|
|||||||
private int lockVideoOrientation = -1;
|
private int lockVideoOrientation = -1;
|
||||||
private boolean tunnelForward;
|
private boolean tunnelForward;
|
||||||
private Rect crop;
|
private Rect crop;
|
||||||
private boolean sendFrameMeta = true; // send PTS so that the client may record properly
|
|
||||||
private boolean control = true;
|
private boolean control = true;
|
||||||
private int displayId;
|
private int displayId;
|
||||||
private boolean showTouches;
|
private boolean showTouches;
|
||||||
@@ -21,6 +20,12 @@ public class Options {
|
|||||||
private String encoderName;
|
private String encoderName;
|
||||||
private boolean powerOffScreenOnClose;
|
private boolean powerOffScreenOnClose;
|
||||||
private boolean clipboardAutosync = true;
|
private boolean clipboardAutosync = true;
|
||||||
|
private boolean downsizeOnError = true;
|
||||||
|
|
||||||
|
// Options not used by the scrcpy client, but useful to use scrcpy-server directly
|
||||||
|
private boolean sendDeviceMeta = true; // send device name and size
|
||||||
|
private boolean sendFrameMeta = true; // send PTS so that the client may record properly
|
||||||
|
private boolean sendDummyByte = true; // write a byte on start to detect connection issues
|
||||||
|
|
||||||
public Ln.Level getLogLevel() {
|
public Ln.Level getLogLevel() {
|
||||||
return logLevel;
|
return logLevel;
|
||||||
@@ -78,14 +83,6 @@ public class Options {
|
|||||||
this.crop = crop;
|
this.crop = crop;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getSendFrameMeta() {
|
|
||||||
return sendFrameMeta;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSendFrameMeta(boolean sendFrameMeta) {
|
|
||||||
this.sendFrameMeta = sendFrameMeta;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getControl() {
|
public boolean getControl() {
|
||||||
return control;
|
return control;
|
||||||
}
|
}
|
||||||
@@ -149,4 +146,36 @@ public class Options {
|
|||||||
public void setClipboardAutosync(boolean clipboardAutosync) {
|
public void setClipboardAutosync(boolean clipboardAutosync) {
|
||||||
this.clipboardAutosync = clipboardAutosync;
|
this.clipboardAutosync = clipboardAutosync;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getDownsizeOnError() {
|
||||||
|
return downsizeOnError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDownsizeOnError(boolean downsizeOnError) {
|
||||||
|
this.downsizeOnError = downsizeOnError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getSendDeviceMeta() {
|
||||||
|
return sendDeviceMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSendDeviceMeta(boolean sendDeviceMeta) {
|
||||||
|
this.sendDeviceMeta = sendDeviceMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getSendFrameMeta() {
|
||||||
|
return sendFrameMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSendFrameMeta(boolean sendFrameMeta) {
|
||||||
|
this.sendFrameMeta = sendFrameMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getSendDummyByte() {
|
||||||
|
return sendDummyByte;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSendDummyByte(boolean sendDummyByte) {
|
||||||
|
this.sendDummyByte = sendDummyByte;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms
|
private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms
|
||||||
private static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder";
|
private static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder";
|
||||||
|
|
||||||
|
// Keep the values in descending order
|
||||||
|
private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800};
|
||||||
|
|
||||||
private static final int NO_PTS = -1;
|
private static final int NO_PTS = -1;
|
||||||
|
|
||||||
private final AtomicBoolean rotationChanged = new AtomicBoolean();
|
private final AtomicBoolean rotationChanged = new AtomicBoolean();
|
||||||
@@ -35,14 +38,19 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
private final int bitRate;
|
private final int bitRate;
|
||||||
private final int maxFps;
|
private final int maxFps;
|
||||||
private final boolean sendFrameMeta;
|
private final boolean sendFrameMeta;
|
||||||
|
private final boolean downsizeOnError;
|
||||||
private long ptsOrigin;
|
private long ptsOrigin;
|
||||||
|
|
||||||
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List<CodecOption> codecOptions, String encoderName) {
|
private boolean firstFrameSent;
|
||||||
|
|
||||||
|
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List<CodecOption> codecOptions, String encoderName,
|
||||||
|
boolean downsizeOnError) {
|
||||||
this.sendFrameMeta = sendFrameMeta;
|
this.sendFrameMeta = sendFrameMeta;
|
||||||
this.bitRate = bitRate;
|
this.bitRate = bitRate;
|
||||||
this.maxFps = maxFps;
|
this.maxFps = maxFps;
|
||||||
this.codecOptions = codecOptions;
|
this.codecOptions = codecOptions;
|
||||||
this.encoderName = encoderName;
|
this.encoderName = encoderName;
|
||||||
|
this.downsizeOnError = downsizeOnError;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -91,6 +99,23 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
alive = encode(codec, fd);
|
alive = encode(codec, fd);
|
||||||
// do not call stop() on exception, it would trigger an IllegalStateException
|
// do not call stop() on exception, it would trigger an IllegalStateException
|
||||||
codec.stop();
|
codec.stop();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage());
|
||||||
|
if (!downsizeOnError || firstFrameSent) {
|
||||||
|
// Fail immediately
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newMaxSize = chooseMaxSizeFallback(screenInfo.getVideoSize());
|
||||||
|
if (newMaxSize == 0) {
|
||||||
|
// Definitively fail
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retry with a smaller device size
|
||||||
|
Ln.i("Retrying with -m" + newMaxSize + "...");
|
||||||
|
device.setMaxSize(newMaxSize);
|
||||||
|
alive = true;
|
||||||
} finally {
|
} finally {
|
||||||
destroyDisplay(display);
|
destroyDisplay(display);
|
||||||
codec.release();
|
codec.release();
|
||||||
@@ -102,6 +127,18 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int chooseMaxSizeFallback(Size failedSize) {
|
||||||
|
int currentMaxSize = Math.max(failedSize.getWidth(), failedSize.getHeight());
|
||||||
|
for (int value : MAX_SIZE_FALLBACK) {
|
||||||
|
if (value < currentMaxSize) {
|
||||||
|
// We found a smaller value to reduce the video size
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No fallback, fail definitively
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException {
|
private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException {
|
||||||
boolean eof = false;
|
boolean eof = false;
|
||||||
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||||
@@ -122,6 +159,10 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IO.writeFully(fd, codecBuffer);
|
IO.writeFully(fd, codecBuffer);
|
||||||
|
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
|
||||||
|
// If this is not a config packet, then it contains a frame
|
||||||
|
firstFrameSent = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (outputBufferId >= 0) {
|
if (outputBufferId >= 0) {
|
||||||
|
|||||||
@@ -80,15 +80,12 @@ public final class ScreenInfo {
|
|||||||
return new ScreenInfo(newContentRect, newUnlockedVideoSize, newDeviceRotation, lockedVideoOrientation);
|
return new ScreenInfo(newContentRect, newUnlockedVideoSize, newDeviceRotation, lockedVideoOrientation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize, int lockedVideoOrientation) {
|
public static ScreenInfo computeScreenInfo(int rotation, Size deviceSize, Rect crop, int maxSize, int lockedVideoOrientation) {
|
||||||
int rotation = displayInfo.getRotation();
|
|
||||||
|
|
||||||
if (lockedVideoOrientation == Device.LOCK_VIDEO_ORIENTATION_INITIAL) {
|
if (lockedVideoOrientation == Device.LOCK_VIDEO_ORIENTATION_INITIAL) {
|
||||||
// The user requested to lock the video orientation to the current orientation
|
// The user requested to lock the video orientation to the current orientation
|
||||||
lockedVideoOrientation = rotation;
|
lockedVideoOrientation = rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
Size deviceSize = displayInfo.getSize();
|
|
||||||
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
|
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
|
||||||
if (crop != null) {
|
if (crop != null) {
|
||||||
if (rotation % 2 != 0) { // 180s preserve dimensions
|
if (rotation % 2 != 0) { // 180s preserve dimensions
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.media.MediaCodec;
|
|
||||||
import android.media.MediaCodecInfo;
|
import android.media.MediaCodecInfo;
|
||||||
import android.os.BatteryManager;
|
import android.os.BatteryManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
@@ -67,10 +66,15 @@ public final class Server {
|
|||||||
|
|
||||||
boolean tunnelForward = options.isTunnelForward();
|
boolean tunnelForward = options.isTunnelForward();
|
||||||
boolean control = options.getControl();
|
boolean control = options.getControl();
|
||||||
|
boolean sendDummyByte = options.getSendDummyByte();
|
||||||
|
|
||||||
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward, control)) {
|
try (DesktopConnection connection = DesktopConnection.open(tunnelForward, control, sendDummyByte)) {
|
||||||
|
if (options.getSendDeviceMeta()) {
|
||||||
|
Size videoSize = device.getScreenInfo().getVideoSize();
|
||||||
|
connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
|
||||||
|
}
|
||||||
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions,
|
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions,
|
||||||
options.getEncoderName());
|
options.getEncoderName(), options.getDownsizeOnError());
|
||||||
|
|
||||||
Thread controllerThread = null;
|
Thread controllerThread = null;
|
||||||
Thread deviceMessageSenderThread = null;
|
Thread deviceMessageSenderThread = null;
|
||||||
@@ -200,10 +204,6 @@ public final class Server {
|
|||||||
Rect crop = parseCrop(value);
|
Rect crop = parseCrop(value);
|
||||||
options.setCrop(crop);
|
options.setCrop(crop);
|
||||||
break;
|
break;
|
||||||
case "send_frame_meta":
|
|
||||||
boolean sendFrameMeta = Boolean.parseBoolean(value);
|
|
||||||
options.setSendFrameMeta(sendFrameMeta);
|
|
||||||
break;
|
|
||||||
case "control":
|
case "control":
|
||||||
boolean control = Boolean.parseBoolean(value);
|
boolean control = Boolean.parseBoolean(value);
|
||||||
options.setControl(control);
|
options.setControl(control);
|
||||||
@@ -237,6 +237,29 @@ public final class Server {
|
|||||||
boolean clipboardAutosync = Boolean.parseBoolean(value);
|
boolean clipboardAutosync = Boolean.parseBoolean(value);
|
||||||
options.setClipboardAutosync(clipboardAutosync);
|
options.setClipboardAutosync(clipboardAutosync);
|
||||||
break;
|
break;
|
||||||
|
case "downsize_on_error":
|
||||||
|
boolean downsizeOnError = Boolean.parseBoolean(value);
|
||||||
|
options.setDownsizeOnError(downsizeOnError);
|
||||||
|
break;
|
||||||
|
case "send_device_meta":
|
||||||
|
boolean sendDeviceMeta = Boolean.parseBoolean(value);
|
||||||
|
options.setSendDeviceMeta(sendDeviceMeta);
|
||||||
|
break;
|
||||||
|
case "send_frame_meta":
|
||||||
|
boolean sendFrameMeta = Boolean.parseBoolean(value);
|
||||||
|
options.setSendFrameMeta(sendFrameMeta);
|
||||||
|
break;
|
||||||
|
case "send_dummy_byte":
|
||||||
|
boolean sendDummyByte = Boolean.parseBoolean(value);
|
||||||
|
options.setSendDummyByte(sendDummyByte);
|
||||||
|
break;
|
||||||
|
case "raw_video_stream":
|
||||||
|
boolean rawVideoStream = Boolean.parseBoolean(value);
|
||||||
|
if (rawVideoStream) {
|
||||||
|
options.setSendDeviceMeta(false);
|
||||||
|
options.setSendFrameMeta(false);
|
||||||
|
options.setSendDummyByte(false);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
Ln.w("Unknown server option: " + key);
|
Ln.w("Unknown server option: " + key);
|
||||||
break;
|
break;
|
||||||
@@ -263,16 +286,6 @@ public final class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void suggestFix(Throwable e) {
|
private static void suggestFix(Throwable e) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
if (e instanceof MediaCodec.CodecException) {
|
|
||||||
MediaCodec.CodecException mce = (MediaCodec.CodecException) e;
|
|
||||||
if (mce.getErrorCode() == 0xfffffc0e) {
|
|
||||||
Ln.e("The hardware encoder is not able to encode at the given definition.");
|
|
||||||
Ln.e("Try with a lower definition:");
|
|
||||||
Ln.e(" scrcpy -m 1024");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (e instanceof InvalidDisplayIdException) {
|
if (e instanceof InvalidDisplayIdException) {
|
||||||
InvalidDisplayIdException idie = (InvalidDisplayIdException) e;
|
InvalidDisplayIdException idie = (InvalidDisplayIdException) e;
|
||||||
int[] displayIds = idie.getAvailableDisplayIds();
|
int[] displayIds = idie.getAvailableDisplayIds();
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public final class InputManager {
|
|||||||
|
|
||||||
private final IInterface manager;
|
private final IInterface manager;
|
||||||
private Method injectInputEventMethod;
|
private Method injectInputEventMethod;
|
||||||
|
private boolean alternativeInjectInputEventMethod;
|
||||||
|
|
||||||
private static Method setDisplayIdMethod;
|
private static Method setDisplayIdMethod;
|
||||||
|
|
||||||
@@ -25,7 +26,12 @@ public final class InputManager {
|
|||||||
|
|
||||||
private Method getInjectInputEventMethod() throws NoSuchMethodException {
|
private Method getInjectInputEventMethod() throws NoSuchMethodException {
|
||||||
if (injectInputEventMethod == null) {
|
if (injectInputEventMethod == null) {
|
||||||
|
try {
|
||||||
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
|
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class, int.class);
|
||||||
|
alternativeInjectInputEventMethod = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return injectInputEventMethod;
|
return injectInputEventMethod;
|
||||||
}
|
}
|
||||||
@@ -33,6 +39,10 @@ public final class InputManager {
|
|||||||
public boolean injectInputEvent(InputEvent inputEvent, int mode) {
|
public boolean injectInputEvent(InputEvent inputEvent, int mode) {
|
||||||
try {
|
try {
|
||||||
Method method = getInjectInputEventMethod();
|
Method method = getInjectInputEventMethod();
|
||||||
|
if (alternativeInjectInputEventMethod) {
|
||||||
|
// See <https://github.com/Genymobile/scrcpy/issues/2250>
|
||||||
|
return (boolean) method.invoke(manager, inputEvent, mode, 0);
|
||||||
|
}
|
||||||
return (boolean) method.invoke(manager, inputEvent, mode);
|
return (boolean) method.invoke(manager, inputEvent, mode);
|
||||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||||
Ln.e("Could not invoke method", e);
|
Ln.e("Could not invoke method", e);
|
||||||
|
|||||||
Reference in New Issue
Block a user