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
|
||||
```
|
||||
|
||||
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).
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ src = [
|
||||
'src/decoder.c',
|
||||
'src/device_msg.c',
|
||||
'src/icon.c',
|
||||
'src/file_handler.c',
|
||||
'src/file_pusher.c',
|
||||
'src/fps_counter.c',
|
||||
'src/frame_buffer.c',
|
||||
'src/input_manager.c',
|
||||
@@ -118,15 +118,20 @@ else
|
||||
include_directories: include_directories(sdl2_include_dir)
|
||||
)
|
||||
|
||||
prebuilt_ffmpeg_shared = meson.get_cross_property('prebuilt_ffmpeg_shared')
|
||||
prebuilt_ffmpeg_dev = meson.get_cross_property('prebuilt_ffmpeg_dev')
|
||||
ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg_shared + '/bin'
|
||||
ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg_dev + '/include'
|
||||
prebuilt_ffmpeg = meson.get_cross_property('prebuilt_ffmpeg')
|
||||
ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg + '/bin'
|
||||
ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg + '/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(
|
||||
dependencies: [
|
||||
cc.find_library('avcodec-58', dirs: ffmpeg_bin_dir),
|
||||
cc.find_library('avformat-58', dirs: ffmpeg_bin_dir),
|
||||
cc.find_library('avutil-56', dirs: ffmpeg_bin_dir),
|
||||
cc.find_library(ffmpeg_avcodec, dirs: ffmpeg_bin_dir),
|
||||
cc.find_library(ffmpeg_avformat, dirs: ffmpeg_bin_dir),
|
||||
cc.find_library(ffmpeg_avutil, dirs: ffmpeg_bin_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.
|
||||
|
||||
.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
|
||||
.B \-n, \-\-no\-control
|
||||
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
|
||||
|
||||
struct libusb_device_descriptor desc;
|
||||
libusb_get_device_descriptor(device, &desc);
|
||||
|
||||
if (!desc.iSerialNumber) {
|
||||
int result = libusb_get_device_descriptor(device, &desc);
|
||||
if (result < 0 || !desc.iSerialNumber) {
|
||||
return false;
|
||||
}
|
||||
|
||||
libusb_device_handle *handle;
|
||||
int result = libusb_open(device, &handle);
|
||||
result = libusb_open(device, &handle);
|
||||
if (result < 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -131,31 +130,22 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial,
|
||||
}
|
||||
|
||||
if (!sc_cond_init(&aoa->event_cond)) {
|
||||
sc_mutex_destroy(&aoa->mutex);
|
||||
return false;
|
||||
goto error_destroy_mutex;
|
||||
}
|
||||
|
||||
if (libusb_init(&aoa->usb_context) != LIBUSB_SUCCESS) {
|
||||
sc_cond_destroy(&aoa->event_cond);
|
||||
sc_mutex_destroy(&aoa->mutex);
|
||||
return false;
|
||||
goto error_destroy_cond;
|
||||
}
|
||||
|
||||
aoa->usb_device = sc_aoa_find_usb_device(serial);
|
||||
if (!aoa->usb_device) {
|
||||
LOGW("USB device of serial %s not found", serial);
|
||||
libusb_exit(aoa->usb_context);
|
||||
sc_mutex_destroy(&aoa->mutex);
|
||||
sc_cond_destroy(&aoa->event_cond);
|
||||
return false;
|
||||
goto error_exit_libusb;
|
||||
}
|
||||
|
||||
if (sc_aoa_open_usb_handle(aoa->usb_device, &aoa->usb_handle) < 0) {
|
||||
LOGW("Open USB handle failed");
|
||||
libusb_unref_device(aoa->usb_device);
|
||||
libusb_exit(aoa->usb_context);
|
||||
sc_cond_destroy(&aoa->event_cond);
|
||||
sc_mutex_destroy(&aoa->mutex);
|
||||
goto error_unref_device;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -163,6 +153,16 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial,
|
||||
aoa->acksync = acksync;
|
||||
|
||||
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
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
#define OPT_NO_CLIPBOARD_AUTOSYNC 1032
|
||||
#define OPT_TCPIP 1033
|
||||
#define OPT_RAW_KEY_EVENTS 1034
|
||||
#define OPT_NO_DOWNSIZE_ON_ERROR 1035
|
||||
|
||||
struct sc_option {
|
||||
char shortopt;
|
||||
@@ -236,6 +237,13 @@ static const struct sc_option options[] = {
|
||||
"is preserved.\n"
|
||||
"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 = "no-clipboard-autosync",
|
||||
@@ -1312,12 +1320,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
case 'K':
|
||||
#ifdef HAVE_AOA_HID
|
||||
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID;
|
||||
break;
|
||||
#else
|
||||
LOGE("HID over AOA (-K/--hid-keyboard) is not supported on "
|
||||
"this platform. It is only available on Linux.");
|
||||
return false;
|
||||
#endif
|
||||
break;
|
||||
case OPT_MAX_FPS:
|
||||
if (!parse_max_fps(optarg, &opts->max_fps)) {
|
||||
return false;
|
||||
@@ -1331,12 +1339,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
case 'M':
|
||||
#ifdef HAVE_AOA_HID
|
||||
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID;
|
||||
break;
|
||||
#else
|
||||
LOGE("HID over AOA (-M/--hid-mouse) is not supported on this"
|
||||
"platform. It is only available on Linux.");
|
||||
return false;
|
||||
#endif
|
||||
break;
|
||||
case OPT_LOCK_VIDEO_ORIENTATION:
|
||||
if (!parse_lock_video_orientation(optarg,
|
||||
&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_dst = optarg;
|
||||
break;
|
||||
case OPT_NO_DOWNSIZE_ON_ERROR:
|
||||
opts->downsize_on_error = false;
|
||||
break;
|
||||
case OPT_V4L2_SINK:
|
||||
#ifdef HAVE_V4L2
|
||||
opts->v4l2_device = optarg;
|
||||
break;
|
||||
#else
|
||||
LOGE("V4L2 (--v4l2-sink) is only available on Linux.");
|
||||
return false;
|
||||
#endif
|
||||
break;
|
||||
case OPT_V4L2_BUFFER:
|
||||
#ifdef HAVE_V4L2
|
||||
if (!parse_buffering_time(optarg, &opts->v4l2_buffer)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
#else
|
||||
LOGE("V4L2 (--v4l2-buffer) is only available on Linux.");
|
||||
return false;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
// getopt prints the error message on stderr
|
||||
return false;
|
||||
@@ -1534,11 +1545,18 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->v4l2_device && opts->lock_video_orientation
|
||||
== SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
||||
LOGI("Video orientation is locked for v4l2 sink. "
|
||||
"See --lock-video-orientation.");
|
||||
opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
|
||||
if (opts->v4l2_device) {
|
||||
if (opts->lock_video_orientation ==
|
||||
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
||||
LOGI("Video orientation is locked for v4l2 sink. "
|
||||
"See --lock-video-orientation.");
|
||||
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) {
|
||||
@@ -1573,14 +1591,23 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
}
|
||||
|
||||
if (!opts->control && opts->turn_screen_off) {
|
||||
LOGE("Could not request to turn screen off if control is disabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!opts->control && opts->stay_awake) {
|
||||
LOGE("Could not request to stay awake if control is disabled");
|
||||
return false;
|
||||
if (!opts->control) {
|
||||
if (opts->turn_screen_off) {
|
||||
LOGE("Could not request to turn screen off if control is disabled");
|
||||
return false;
|
||||
}
|
||||
if (opts->stay_awake) {
|
||||
LOGE("Could not request to stay awake if control is disabled");
|
||||
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;
|
||||
|
||||
@@ -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) {
|
||||
bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID);
|
||||
if (!ok) {
|
||||
LOGW("Could not unregister HID");
|
||||
LOGW("Could not unregister HID mouse");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef HID_MOUSE_H
|
||||
#define HID_MOUSE_H
|
||||
#ifndef SC_HID_MOUSE_H
|
||||
#define SC_HID_MOUSE_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
|
||||
@@ -124,15 +124,15 @@ is_shortcut_mod(struct sc_input_manager *im, uint16_t sdl_mod) {
|
||||
void
|
||||
sc_input_manager_init(struct sc_input_manager *im,
|
||||
const struct sc_input_manager_params *params) {
|
||||
assert(!params->control || (params->kp && params->kp->ops));
|
||||
assert(!params->control || (params->mp && params->mp->ops));
|
||||
assert(!params->controller || (params->kp && params->kp->ops));
|
||||
assert(!params->controller || (params->mp && params->mp->ops));
|
||||
|
||||
im->controller = params->controller;
|
||||
im->fp = params->fp;
|
||||
im->screen = params->screen;
|
||||
im->kp = params->kp;
|
||||
im->mp = params->mp;
|
||||
|
||||
im->control = params->control;
|
||||
im->forward_all_clicks = params->forward_all_clicks;
|
||||
im->legacy_paste = params->legacy_paste;
|
||||
im->clipboard_autosync = params->clipboard_autosync;
|
||||
@@ -433,9 +433,7 @@ inverse_point(struct sc_point point, struct sc_size size) {
|
||||
static void
|
||||
sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
const SDL_KeyboardEvent *event) {
|
||||
// control: indicates the state of the command-line option --no-control
|
||||
bool control = im->control;
|
||||
|
||||
// controller is NULL if --no-control is requested
|
||||
struct sc_controller *controller = im->controller;
|
||||
|
||||
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;
|
||||
switch (keycode) {
|
||||
case SDLK_h:
|
||||
if (control && !shift && !repeat) {
|
||||
if (controller && !shift && !repeat) {
|
||||
action_home(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_b: // fall-through
|
||||
case SDLK_BACKSPACE:
|
||||
if (control && !shift && !repeat) {
|
||||
if (controller && !shift && !repeat) {
|
||||
action_back(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_s:
|
||||
if (control && !shift && !repeat) {
|
||||
if (controller && !shift && !repeat) {
|
||||
action_app_switch(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_m:
|
||||
if (control && !shift && !repeat) {
|
||||
if (controller && !shift && !repeat) {
|
||||
action_menu(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_p:
|
||||
if (control && !shift && !repeat) {
|
||||
if (controller && !shift && !repeat) {
|
||||
action_power(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_o:
|
||||
if (control && !repeat && down) {
|
||||
if (controller && !repeat && down) {
|
||||
enum screen_power_mode mode = shift
|
||||
? SCREEN_POWER_MODE_NORMAL
|
||||
: SCREEN_POWER_MODE_OFF;
|
||||
@@ -496,13 +494,13 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_DOWN:
|
||||
if (control && !shift) {
|
||||
if (controller && !shift) {
|
||||
// forward repeated events
|
||||
action_volume_down(controller, action);
|
||||
}
|
||||
return;
|
||||
case SDLK_UP:
|
||||
if (control && !shift) {
|
||||
if (controller && !shift) {
|
||||
// forward repeated events
|
||||
action_volume_up(controller, action);
|
||||
}
|
||||
@@ -518,19 +516,19 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_c:
|
||||
if (control && !shift && !repeat && down) {
|
||||
if (controller && !shift && !repeat && down) {
|
||||
get_device_clipboard(controller,
|
||||
GET_CLIPBOARD_COPY_KEY_COPY);
|
||||
}
|
||||
return;
|
||||
case SDLK_x:
|
||||
if (control && !shift && !repeat && down) {
|
||||
if (controller && !shift && !repeat && down) {
|
||||
get_device_clipboard(controller,
|
||||
GET_CLIPBOARD_COPY_KEY_CUT);
|
||||
}
|
||||
return;
|
||||
case SDLK_v:
|
||||
if (control && !repeat && down) {
|
||||
if (controller && !repeat && down) {
|
||||
if (shift || im->legacy_paste) {
|
||||
// inject the text as input events
|
||||
clipboard_paste(controller);
|
||||
@@ -563,7 +561,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_n:
|
||||
if (control && !repeat && down) {
|
||||
if (controller && !repeat && down) {
|
||||
if (shift) {
|
||||
collapse_panels(controller);
|
||||
} else if (im->key_repeat == 0) {
|
||||
@@ -574,7 +572,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_r:
|
||||
if (control && !shift && !repeat && down) {
|
||||
if (controller && !shift && !repeat && down) {
|
||||
rotate_device(controller);
|
||||
}
|
||||
return;
|
||||
@@ -583,7 +581,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
return;
|
||||
}
|
||||
|
||||
if (!control) {
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -700,7 +698,7 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
|
||||
static void
|
||||
sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
const SDL_MouseButtonEvent *event) {
|
||||
bool control = im->control;
|
||||
struct sc_controller *controller = im->controller;
|
||||
|
||||
if (event->which == SDL_TOUCH_MOUSEID) {
|
||||
// simulated from touch events, so it's a duplicate
|
||||
@@ -709,27 +707,29 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
|
||||
bool down = event->type == SDL_MOUSEBUTTONDOWN;
|
||||
if (!im->forward_all_clicks) {
|
||||
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
||||
if (controller) {
|
||||
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
||||
|
||||
if (control && event->button == SDL_BUTTON_X1) {
|
||||
action_app_switch(im->controller, action);
|
||||
return;
|
||||
}
|
||||
if (control && event->button == SDL_BUTTON_X2 && down) {
|
||||
if (event->clicks < 2) {
|
||||
expand_notification_panel(im->controller);
|
||||
} else {
|
||||
expand_settings_panel(im->controller);
|
||||
if (event->button == SDL_BUTTON_X1) {
|
||||
action_app_switch(controller, action);
|
||||
return;
|
||||
}
|
||||
if (event->button == SDL_BUTTON_X2 && down) {
|
||||
if (event->clicks < 2) {
|
||||
expand_notification_panel(controller);
|
||||
} else {
|
||||
expand_settings_panel(controller);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (event->button == SDL_BUTTON_RIGHT) {
|
||||
press_back_or_turn_screen_on(controller, action);
|
||||
return;
|
||||
}
|
||||
if (event->button == SDL_BUTTON_MIDDLE) {
|
||||
action_home(controller, action);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (control && event->button == SDL_BUTTON_RIGHT) {
|
||||
press_back_or_turn_screen_on(im->controller, action);
|
||||
return;
|
||||
}
|
||||
if (control && event->button == SDL_BUTTON_MIDDLE) {
|
||||
action_home(im->controller, action);
|
||||
return;
|
||||
}
|
||||
|
||||
// double-click on black borders resize to fit the device screen
|
||||
@@ -750,7 +750,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
// otherwise, send the click event to the device
|
||||
}
|
||||
|
||||
if (!control) {
|
||||
if (!controller) {
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
bool control = im->controller;
|
||||
switch (event->type) {
|
||||
case SDL_TEXTINPUT:
|
||||
if (!im->control) {
|
||||
return true;
|
||||
if (!control) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_text_input(im, &event->text);
|
||||
return true;
|
||||
break;
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP:
|
||||
// some key events do not interact with the device, so process the
|
||||
// event even if control is disabled
|
||||
sc_input_manager_process_key(im, &event->key);
|
||||
return true;
|
||||
break;
|
||||
case SDL_MOUSEMOTION:
|
||||
if (!im->control) {
|
||||
if (!control) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_mouse_motion(im, &event->motion);
|
||||
return true;
|
||||
break;
|
||||
case SDL_MOUSEWHEEL:
|
||||
if (!im->control) {
|
||||
if (!control) {
|
||||
break;
|
||||
}
|
||||
sc_input_manager_process_mouse_wheel(im, &event->wheel);
|
||||
return true;
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
// some mouse events do not interact with the device, so process
|
||||
// the event even if control is disabled
|
||||
sc_input_manager_process_mouse_button(im, &event->button);
|
||||
return true;
|
||||
break;
|
||||
case SDL_FINGERMOTION:
|
||||
case SDL_FINGERDOWN:
|
||||
case SDL_FINGERUP:
|
||||
if (!control) {
|
||||
break;
|
||||
}
|
||||
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 "controller.h"
|
||||
#include "file_pusher.h"
|
||||
#include "fps_counter.h"
|
||||
#include "options.h"
|
||||
#include "trait/key_processor.h"
|
||||
@@ -15,12 +16,12 @@
|
||||
|
||||
struct sc_input_manager {
|
||||
struct sc_controller *controller;
|
||||
struct sc_file_pusher *fp;
|
||||
struct sc_screen *screen;
|
||||
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
|
||||
bool control;
|
||||
bool forward_all_clicks;
|
||||
bool legacy_paste;
|
||||
bool clipboard_autosync;
|
||||
@@ -44,11 +45,11 @@ struct sc_input_manager {
|
||||
|
||||
struct sc_input_manager_params {
|
||||
struct sc_controller *controller;
|
||||
struct sc_file_pusher *fp;
|
||||
struct sc_screen *screen;
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
|
||||
bool control;
|
||||
bool forward_all_clicks;
|
||||
bool legacy_paste;
|
||||
bool clipboard_autosync;
|
||||
@@ -59,7 +60,7 @@ void
|
||||
sc_input_manager_init(struct sc_input_manager *im,
|
||||
const struct sc_input_manager_params *params);
|
||||
|
||||
bool
|
||||
void
|
||||
sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -54,6 +54,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.legacy_paste = false,
|
||||
.power_off_on_close = false,
|
||||
.clipboard_autosync = true,
|
||||
.downsize_on_error = true,
|
||||
.tcpip = false,
|
||||
.tcpip_dst = NULL,
|
||||
};
|
||||
|
||||
@@ -129,6 +129,7 @@ struct scrcpy_options {
|
||||
bool legacy_paste;
|
||||
bool power_off_on_close;
|
||||
bool clipboard_autosync;
|
||||
bool downsize_on_error;
|
||||
bool tcpip;
|
||||
const char *tcpip_dst;
|
||||
};
|
||||
|
||||
106
app/src/scrcpy.c
106
app/src/scrcpy.c
@@ -16,7 +16,7 @@
|
||||
#include "controller.h"
|
||||
#include "decoder.h"
|
||||
#include "events.h"
|
||||
#include "file_handler.h"
|
||||
#include "file_pusher.h"
|
||||
#ifdef HAVE_AOA_HID
|
||||
# include "hid_keyboard.h"
|
||||
# include "hid_mouse.h"
|
||||
@@ -44,7 +44,7 @@ struct scrcpy {
|
||||
struct sc_v4l2_sink v4l2_sink;
|
||||
#endif
|
||||
struct sc_controller controller;
|
||||
struct file_handler file_handler;
|
||||
struct sc_file_pusher file_pusher;
|
||||
#ifdef HAVE_AOA_HID
|
||||
struct sc_aoa aoa;
|
||||
// sequence/ack helper to synchronize clipboard and Ctrl+v via HID
|
||||
@@ -149,68 +149,18 @@ sdl_configure(bool display, bool disable_screensaver) {
|
||||
}
|
||||
|
||||
static bool
|
||||
is_apk(const char *file) {
|
||||
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) {
|
||||
event_loop(struct scrcpy *s) {
|
||||
SDL_Event event;
|
||||
while (SDL_WaitEvent(&event)) {
|
||||
enum event_result result = handle_event(s, options, &event);
|
||||
switch (result) {
|
||||
case EVENT_RESULT_STOPPED_BY_USER:
|
||||
return true;
|
||||
case EVENT_RESULT_STOPPED_BY_EOS:
|
||||
switch (event.type) {
|
||||
case EVENT_STREAM_STOPPED:
|
||||
LOGW("Device disconnected");
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -327,7 +277,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
bool ret = false;
|
||||
|
||||
bool server_started = false;
|
||||
bool file_handler_initialized = false;
|
||||
bool file_pusher_initialized = false;
|
||||
bool recorder_initialized = false;
|
||||
#ifdef HAVE_V4L2
|
||||
bool v4l2_sink_initialized = false;
|
||||
@@ -364,6 +314,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
.force_adb_forward = options->force_adb_forward,
|
||||
.power_off_on_close = options->power_off_on_close,
|
||||
.clipboard_autosync = options->clipboard_autosync,
|
||||
.downsize_on_error = options->downsize_on_error,
|
||||
.tcpip = options->tcpip,
|
||||
.tcpip_dst = options->tcpip_dst,
|
||||
};
|
||||
@@ -406,12 +357,15 @@ scrcpy(struct scrcpy_options *options) {
|
||||
const char *serial = s->server.params.serial;
|
||||
assert(serial);
|
||||
|
||||
struct sc_file_pusher *fp = NULL;
|
||||
|
||||
if (options->display && options->control) {
|
||||
if (!file_handler_init(&s->file_handler, serial,
|
||||
options->push_target)) {
|
||||
if (!sc_file_pusher_init(&s->file_pusher, serial,
|
||||
options->push_target)) {
|
||||
goto end;
|
||||
}
|
||||
file_handler_initialized = true;
|
||||
fp = &s->file_pusher;
|
||||
file_pusher_initialized = true;
|
||||
}
|
||||
|
||||
struct decoder *dec = NULL;
|
||||
@@ -451,6 +405,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
stream_add_sink(&s->stream, &rec->packet_sink);
|
||||
}
|
||||
|
||||
struct sc_controller *controller = NULL;
|
||||
struct sc_key_processor *kp = NULL;
|
||||
struct sc_mouse_processor *mp = NULL;
|
||||
|
||||
@@ -556,6 +511,7 @@ aoa_hid_end:
|
||||
goto end;
|
||||
}
|
||||
controller_started = true;
|
||||
controller = &s->controller;
|
||||
|
||||
if (options->turn_screen_off) {
|
||||
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) {
|
||||
const char *window_title =
|
||||
options->window_title ? options->window_title : info->device_name;
|
||||
|
||||
struct sc_screen_params screen_params = {
|
||||
.controller = &s->controller,
|
||||
.controller = controller,
|
||||
.fp = fp,
|
||||
.kp = kp,
|
||||
.mp = mp,
|
||||
.control = options->control,
|
||||
.forward_all_clicks = options->forward_all_clicks,
|
||||
.legacy_paste = options->legacy_paste,
|
||||
.clipboard_autosync = options->clipboard_autosync,
|
||||
@@ -624,7 +583,7 @@ aoa_hid_end:
|
||||
}
|
||||
stream_started = true;
|
||||
|
||||
ret = event_loop(s, options);
|
||||
ret = event_loop(s);
|
||||
LOGD("quit...");
|
||||
|
||||
// Close the window immediately on closing, because screen_destroy() may
|
||||
@@ -639,6 +598,9 @@ end:
|
||||
if (hid_keyboard_initialized) {
|
||||
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
||||
}
|
||||
if (hid_mouse_initialized) {
|
||||
sc_hid_mouse_destroy(&s->mouse_hid);
|
||||
}
|
||||
sc_aoa_stop(&s->aoa);
|
||||
}
|
||||
if (acksync) {
|
||||
@@ -648,8 +610,8 @@ end:
|
||||
if (controller_started) {
|
||||
sc_controller_stop(&s->controller);
|
||||
}
|
||||
if (file_handler_initialized) {
|
||||
file_handler_stop(&s->file_handler);
|
||||
if (file_pusher_initialized) {
|
||||
sc_file_pusher_stop(&s->file_pusher);
|
||||
}
|
||||
if (screen_initialized) {
|
||||
sc_screen_interrupt(&s->screen);
|
||||
@@ -697,9 +659,9 @@ end:
|
||||
recorder_destroy(&s->recorder);
|
||||
}
|
||||
|
||||
if (file_handler_initialized) {
|
||||
file_handler_join(&s->file_handler);
|
||||
file_handler_destroy(&s->file_handler);
|
||||
if (file_pusher_initialized) {
|
||||
sc_file_pusher_join(&s->file_pusher);
|
||||
sc_file_pusher_destroy(&s->file_pusher);
|
||||
}
|
||||
|
||||
sc_server_destroy(&s->server);
|
||||
|
||||
129
app/src/screen.c
129
app/src/screen.c
@@ -156,7 +156,13 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
|
||||
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) {
|
||||
if (SDL_SetRelativeMouseMode(capture)) {
|
||||
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_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 = {
|
||||
.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);
|
||||
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
|
||||
| SDL_WINDOW_RESIZABLE
|
||||
| SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
@@ -410,13 +419,9 @@ sc_screen_init(struct sc_screen *screen,
|
||||
window_flags |= SDL_WINDOW_BORDERLESS;
|
||||
}
|
||||
|
||||
int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED
|
||||
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED;
|
||||
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED
|
||||
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED;
|
||||
screen->window = SDL_CreateWindow(params->window_title, x, y,
|
||||
window_size.width, window_size.height,
|
||||
window_flags);
|
||||
// The window will be positioned and sized on first video frame
|
||||
screen->window =
|
||||
SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags);
|
||||
if (!screen->window) {
|
||||
LOGC("Could not create window: %s", SDL_GetError());
|
||||
goto error_destroy_fps_counter;
|
||||
@@ -486,10 +491,10 @@ sc_screen_init(struct sc_screen *screen,
|
||||
|
||||
struct sc_input_manager_params im_params = {
|
||||
.controller = params->controller,
|
||||
.fp = params->fp,
|
||||
.screen = screen,
|
||||
.kp = params->kp,
|
||||
.mp = params->mp,
|
||||
.control = params->control,
|
||||
.forward_all_clicks = params->forward_all_clicks,
|
||||
.legacy_paste = params->legacy_paste,
|
||||
.clipboard_autosync = params->clipboard_autosync,
|
||||
@@ -498,17 +503,6 @@ sc_screen_init(struct sc_screen *screen,
|
||||
|
||||
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
|
||||
SDL_AddEventWatch(event_watcher, screen);
|
||||
#endif
|
||||
@@ -545,7 +539,23 @@ error_destroy_video_buffer:
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -693,6 +703,17 @@ sc_screen_update_frame(struct sc_screen *screen) {
|
||||
}
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
bool
|
||||
void
|
||||
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
|
||||
bool relative_mode = sc_screen_is_relative_mode(screen);
|
||||
|
||||
switch (event->type) {
|
||||
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);
|
||||
}
|
||||
case EVENT_NEW_FRAME: {
|
||||
bool ok = sc_screen_update_frame(screen);
|
||||
if (!ok) {
|
||||
LOGW("Frame update failed\n");
|
||||
}
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
case SDL_WINDOWEVENT:
|
||||
if (!screen->has_frame) {
|
||||
// Do nothing
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
switch (event->window.event) {
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
@@ -803,70 +822,72 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
|
||||
sc_screen_render(screen, true);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
if (screen->im.mp->relative_mode) {
|
||||
if (relative_mode) {
|
||||
sc_screen_capture_mouse(screen, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
return;
|
||||
case SDL_KEYDOWN:
|
||||
if (screen->im.mp->relative_mode) {
|
||||
if (relative_mode) {
|
||||
SDL_Keycode key = event->key.keysym.sym;
|
||||
if (sc_screen_is_mouse_capture_key(key)) {
|
||||
if (!screen->mouse_capture_key_pressed) {
|
||||
screen->mouse_capture_key_pressed = key;
|
||||
return true;
|
||||
} else {
|
||||
// Another mouse capture key has been pressed, cancel
|
||||
// mouse (un)capture
|
||||
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;
|
||||
case SDL_KEYUP:
|
||||
if (screen->im.mp->relative_mode) {
|
||||
if (relative_mode) {
|
||||
SDL_Keycode key = event->key.keysym.sym;
|
||||
SDL_Keycode cap = screen->mouse_capture_key_pressed;
|
||||
screen->mouse_capture_key_pressed = 0;
|
||||
if (key == cap) {
|
||||
// A mouse capture key has been pressed then released:
|
||||
// toggle the capture mouse mode
|
||||
sc_screen_capture_mouse(screen, !screen->mouse_captured);
|
||||
return true;
|
||||
if (sc_screen_is_mouse_capture_key(key)) {
|
||||
if (key == cap) {
|
||||
// A mouse capture key has been pressed then released:
|
||||
// toggle the capture mouse mode
|
||||
sc_screen_capture_mouse(screen,
|
||||
!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;
|
||||
case SDL_MOUSEWHEEL:
|
||||
case SDL_MOUSEMOTION:
|
||||
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
|
||||
// on SDL_MOUSEBUTTONUP
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case SDL_FINGERMOTION:
|
||||
case SDL_FINGERDOWN:
|
||||
case SDL_FINGERUP:
|
||||
if (screen->im.mp->relative_mode) {
|
||||
if (relative_mode) {
|
||||
// Touch events are not compatible with relative mode
|
||||
// (coordinates are not relative)
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
if (screen->im.mp->relative_mode && !screen->mouse_captured) {
|
||||
if (relative_mode && !screen->mouse_captured) {
|
||||
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
|
||||
|
||||
@@ -28,6 +28,15 @@ struct sc_screen {
|
||||
struct sc_video_buffer vb;
|
||||
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_Renderer *renderer;
|
||||
SDL_Texture *texture;
|
||||
@@ -61,10 +70,10 @@ struct sc_screen {
|
||||
|
||||
struct sc_screen_params {
|
||||
struct sc_controller *controller;
|
||||
struct sc_file_pusher *fp;
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
|
||||
bool control;
|
||||
bool forward_all_clicks;
|
||||
bool legacy_paste;
|
||||
bool clipboard_autosync;
|
||||
@@ -74,10 +83,10 @@ struct sc_screen_params {
|
||||
struct sc_size frame_size;
|
||||
bool always_on_top;
|
||||
|
||||
int16_t window_x;
|
||||
int16_t window_y;
|
||||
uint16_t window_width; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||
uint16_t window_height; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||
int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||
int16_t window_y; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||
uint16_t window_width;
|
||||
uint16_t window_height;
|
||||
|
||||
bool window_borderless;
|
||||
|
||||
@@ -130,7 +139,7 @@ void
|
||||
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation);
|
||||
|
||||
// react to SDL events
|
||||
bool
|
||||
void
|
||||
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event);
|
||||
|
||||
// convert point from window coordinates to frame coordinates
|
||||
|
||||
@@ -234,6 +234,10 @@ execute_server(struct sc_server *server,
|
||||
// By default, clipboard_autosync is true
|
||||
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 STRBOOL
|
||||
|
||||
@@ -42,6 +42,7 @@ struct sc_server_params {
|
||||
bool force_adb_forward;
|
||||
bool power_off_on_close;
|
||||
bool clipboard_autosync;
|
||||
bool downsize_on_error;
|
||||
bool tcpip;
|
||||
const char *tcpip_dst;
|
||||
};
|
||||
|
||||
@@ -16,6 +16,8 @@ cpu = 'i686'
|
||||
endian = 'little'
|
||||
|
||||
[properties]
|
||||
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win32-shared'
|
||||
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win32-dev'
|
||||
ffmpeg_avcodec = 'avcodec-58'
|
||||
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'
|
||||
|
||||
@@ -16,6 +16,8 @@ cpu = 'x86_64'
|
||||
endian = 'little'
|
||||
|
||||
[properties]
|
||||
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win64-shared'
|
||||
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win64-dev'
|
||||
ffmpeg_avcodec = 'avcodec-59'
|
||||
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'
|
||||
|
||||
@@ -1,33 +1,26 @@
|
||||
.PHONY: prepare-win32 prepare-win64 \
|
||||
prepare-ffmpeg-shared-win32 \
|
||||
prepare-ffmpeg-dev-win32 \
|
||||
prepare-ffmpeg-shared-win64 \
|
||||
prepare-ffmpeg-dev-win64 \
|
||||
prepare-ffmpeg-win32 \
|
||||
prepare-ffmpeg-win64 \
|
||||
prepare-sdl2 \
|
||||
prepare-adb
|
||||
|
||||
prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32 prepare-adb
|
||||
prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb
|
||||
prepare-win32: prepare-sdl2 prepare-ffmpeg-win32 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 \
|
||||
357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2 \
|
||||
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 \
|
||||
230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b \
|
||||
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-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-shared.zip \
|
||||
dd29b7f92f48dead4dd940492c7509138c0f99db445076d0a597007298a79940 \
|
||||
ffmpeg-4.3.1-win64-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-ffmpeg-win64:
|
||||
@./prepare-dep https://github.com/GyanD/codexffmpeg/releases/download/5.0/ffmpeg-5.0-full_build-shared.7z \
|
||||
e5900f6cecd4c438d398bd2fc308736c10b857cd8dd61c11bcfb05bff5d1211a \
|
||||
ffmpeg-5.0-full_build-shared
|
||||
|
||||
prepare-sdl2:
|
||||
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.18-mingw.tar.gz \
|
||||
|
||||
@@ -34,6 +34,9 @@ extract() {
|
||||
elif [[ "$file" == *.tar.gz ]]
|
||||
then
|
||||
tar xf "$file"
|
||||
elif [[ "$file" == *.7z ]]
|
||||
then
|
||||
7z x "$file"
|
||||
else
|
||||
echo "Unsupported file: $file"
|
||||
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-noconsole.vbs "$(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-4.3.1-win64-shared/bin/avcodec-58.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-4.3.1-win64-shared/bin/swresample-3.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/avutil-57.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-5.0-full_build-shared/bin/avformat-59.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-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/AdbWinApi.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;
|
||||
}
|
||||
|
||||
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 controlSocket = null;
|
||||
if (tunnelForward) {
|
||||
LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME);
|
||||
try {
|
||||
videoSocket = localServerSocket.accept();
|
||||
// send one byte so the client may read() to detect a connection error
|
||||
videoSocket.getOutputStream().write(0);
|
||||
if (sendDummyByte) {
|
||||
// send one byte so the client may read() to detect a connection error
|
||||
videoSocket.getOutputStream().write(0);
|
||||
}
|
||||
if (control) {
|
||||
try {
|
||||
controlSocket = localServerSocket.accept();
|
||||
@@ -78,10 +80,7 @@ public final class DesktopConnection implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
DesktopConnection connection = new DesktopConnection(videoSocket, controlSocket);
|
||||
Size videoSize = device.getScreenInfo().getVideoSize();
|
||||
connection.send(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
|
||||
return connection;
|
||||
return new DesktopConnection(videoSocket, controlSocket);
|
||||
}
|
||||
|
||||
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[] deviceNameBytes = deviceName.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
@@ -42,6 +42,11 @@ public final class Device {
|
||||
void onClipboardTextChanged(String text);
|
||||
}
|
||||
|
||||
private final Size deviceSize;
|
||||
private final Rect crop;
|
||||
private int maxSize;
|
||||
private final int lockVideoOrientation;
|
||||
|
||||
private ScreenInfo screenInfo;
|
||||
private RotationListener rotationListener;
|
||||
private ClipboardListener clipboardListener;
|
||||
@@ -69,7 +74,12 @@ public final class Device {
|
||||
|
||||
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();
|
||||
|
||||
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() {
|
||||
return screenInfo;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ public class Options {
|
||||
private int lockVideoOrientation = -1;
|
||||
private boolean tunnelForward;
|
||||
private Rect crop;
|
||||
private boolean sendFrameMeta = true; // send PTS so that the client may record properly
|
||||
private boolean control = true;
|
||||
private int displayId;
|
||||
private boolean showTouches;
|
||||
@@ -21,6 +20,12 @@ public class Options {
|
||||
private String encoderName;
|
||||
private boolean powerOffScreenOnClose;
|
||||
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() {
|
||||
return logLevel;
|
||||
@@ -78,14 +83,6 @@ public class Options {
|
||||
this.crop = crop;
|
||||
}
|
||||
|
||||
public boolean getSendFrameMeta() {
|
||||
return sendFrameMeta;
|
||||
}
|
||||
|
||||
public void setSendFrameMeta(boolean sendFrameMeta) {
|
||||
this.sendFrameMeta = sendFrameMeta;
|
||||
}
|
||||
|
||||
public boolean getControl() {
|
||||
return control;
|
||||
}
|
||||
@@ -149,4 +146,36 @@ public class Options {
|
||||
public void setClipboardAutosync(boolean 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 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 final AtomicBoolean rotationChanged = new AtomicBoolean();
|
||||
@@ -35,14 +38,19 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
private final int bitRate;
|
||||
private final int maxFps;
|
||||
private final boolean sendFrameMeta;
|
||||
private final boolean downsizeOnError;
|
||||
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.bitRate = bitRate;
|
||||
this.maxFps = maxFps;
|
||||
this.codecOptions = codecOptions;
|
||||
this.encoderName = encoderName;
|
||||
this.downsizeOnError = downsizeOnError;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -91,6 +99,23 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
alive = encode(codec, fd);
|
||||
// do not call stop() on exception, it would trigger an IllegalStateException
|
||||
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 {
|
||||
destroyDisplay(display);
|
||||
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 {
|
||||
boolean eof = false;
|
||||
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||
@@ -122,6 +159,10 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
}
|
||||
|
||||
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 {
|
||||
if (outputBufferId >= 0) {
|
||||
|
||||
@@ -80,15 +80,12 @@ public final class ScreenInfo {
|
||||
return new ScreenInfo(newContentRect, newUnlockedVideoSize, newDeviceRotation, lockedVideoOrientation);
|
||||
}
|
||||
|
||||
public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize, int lockedVideoOrientation) {
|
||||
int rotation = displayInfo.getRotation();
|
||||
|
||||
public static ScreenInfo computeScreenInfo(int rotation, Size deviceSize, Rect crop, int maxSize, int lockedVideoOrientation) {
|
||||
if (lockedVideoOrientation == Device.LOCK_VIDEO_ORIENTATION_INITIAL) {
|
||||
// The user requested to lock the video orientation to the current orientation
|
||||
lockedVideoOrientation = rotation;
|
||||
}
|
||||
|
||||
Size deviceSize = displayInfo.getSize();
|
||||
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
|
||||
if (crop != null) {
|
||||
if (rotation % 2 != 0) { // 180s preserve dimensions
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.os.BatteryManager;
|
||||
import android.os.Build;
|
||||
@@ -67,10 +66,15 @@ public final class Server {
|
||||
|
||||
boolean tunnelForward = options.isTunnelForward();
|
||||
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,
|
||||
options.getEncoderName());
|
||||
options.getEncoderName(), options.getDownsizeOnError());
|
||||
|
||||
Thread controllerThread = null;
|
||||
Thread deviceMessageSenderThread = null;
|
||||
@@ -200,10 +204,6 @@ public final class Server {
|
||||
Rect crop = parseCrop(value);
|
||||
options.setCrop(crop);
|
||||
break;
|
||||
case "send_frame_meta":
|
||||
boolean sendFrameMeta = Boolean.parseBoolean(value);
|
||||
options.setSendFrameMeta(sendFrameMeta);
|
||||
break;
|
||||
case "control":
|
||||
boolean control = Boolean.parseBoolean(value);
|
||||
options.setControl(control);
|
||||
@@ -237,6 +237,29 @@ public final class Server {
|
||||
boolean clipboardAutosync = Boolean.parseBoolean(value);
|
||||
options.setClipboardAutosync(clipboardAutosync);
|
||||
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:
|
||||
Ln.w("Unknown server option: " + key);
|
||||
break;
|
||||
@@ -263,16 +286,6 @@ public final class Server {
|
||||
}
|
||||
|
||||
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) {
|
||||
InvalidDisplayIdException idie = (InvalidDisplayIdException) e;
|
||||
int[] displayIds = idie.getAvailableDisplayIds();
|
||||
|
||||
@@ -16,6 +16,7 @@ public final class InputManager {
|
||||
|
||||
private final IInterface manager;
|
||||
private Method injectInputEventMethod;
|
||||
private boolean alternativeInjectInputEventMethod;
|
||||
|
||||
private static Method setDisplayIdMethod;
|
||||
|
||||
@@ -25,7 +26,12 @@ public final class InputManager {
|
||||
|
||||
private Method getInjectInputEventMethod() throws NoSuchMethodException {
|
||||
if (injectInputEventMethod == null) {
|
||||
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
|
||||
try {
|
||||
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;
|
||||
}
|
||||
@@ -33,6 +39,10 @@ public final class InputManager {
|
||||
public boolean injectInputEvent(InputEvent inputEvent, int mode) {
|
||||
try {
|
||||
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);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
|
||||
Reference in New Issue
Block a user