Compare commits

..

46 Commits

Author SHA1 Message Date
Romain Vimont
93ac6a347e Add server option raw_video_stream
For convenience, this new option forces the 3 following options:
 - send_device_meta=false
 - send_frame_meta=false
 - send_dummy_byte=false

This allows to send a raw H.264 stream on the video socket.

Concretely:

    adb push scrcpy-server /data/local/tmp/scrcpy-server.jar
    adb forward tcp:1234 localabstract:scrcpy
    adb shell CLASSPATH=/data/local/tmp/scrcpy-server.jar \
        app_process / com.genymobile.scrcpy.Server 1.21 \
        raw_video_stream=true tunnel_forward=true control=false

As soon as a client connects via TCP to localhost:1234, it will receive
the raw H.264 stream.

Refs #1419 comment <https://github.com/Genymobile/scrcpy/pull/1419#issuecomment-1013964650>
PR #2971 <https://github.com/Genymobile/scrcpy/pull/2971>
2022-01-26 11:30:51 +01:00
Romain Vimont
45a5e560df Add server option send_dummy_byte
If set to false, no dummy byte is written to detect a connection error.

PR #2971 <https://github.com/Genymobile/scrcpy/pull/2971>
2022-01-26 11:30:46 +01:00
Romain Vimont
3ba32c2a0d Add server option send_device_meta
Similar to send_device_frame, this option allows to disable sending the
device name and size on start.

This is only useful when using the scrcpy-server alone to get a raw
H.264 stream, without using the scrcpy client.

PR #2971 <https://github.com/Genymobile/scrcpy/pull/2971>
2022-01-26 11:30:37 +01:00
Romain Vimont
6b21f4ae13 Reorder scrcpy-server options
Move the options unused by the scrcpy client at the end.

These options may be useful to use scrcpy-server directly (to get a raw
H.264 stream for example).

PR #2971 <https://github.com/Genymobile/scrcpy/pull/2971>
2022-01-26 11:30:30 +01:00
Romain Vimont
31a5d0c2bf Move call to send device name and size
This will allow to optionally disable it.

PR #2971 <https://github.com/Genymobile/scrcpy/pull/2971>
2022-01-26 11:30:19 +01:00
Romain Vimont
f289d206ea Disable more actions if --no-control
If control is disabled, then do not enable "show touches" or
automatically power off the device on close.
2022-01-24 21:45:44 +01:00
Romain Vimont
ca516f4318 Refactor if-block in cli
Several tests must be performed if opts->control is false.
2022-01-24 21:44:28 +01:00
Romain Vimont
5d6076bffd Move misplaced break statements
With ifdefs, the resulting code could contain both a return statement
and a break.
2022-01-24 21:38:30 +01:00
Romain Vimont
e0bce1725b Fix header guard prefix 2022-01-24 21:37:40 +01:00
Romain Vimont
a9429efa34 Fix downsize on error before first frame
Retry with a lower definition if MediaCodec fails before the first
frame, not the first packet.

In practice, the first packet is a config packet without any frame, and
MediaCodec might fail just after.

Refs 2eb6fe7d81
Refs #2963 <https://github.com/Genymobile/scrcpy/issues/2963>
2022-01-23 21:46:57 +01:00
Romain Vimont
063d103dd6 Capture mouse on start for --hid-mouse
If relative mode is enabled, capture the mouse immediately.
2022-01-23 21:31:53 +01:00
Romain Vimont
4bf9c057fe Extract relative mode check to an inline function
This will allow to reuse the condition in another function.
2022-01-23 21:31:11 +01:00
Romain Vimont
17c97820b2 Never forward capture keys
In relative mode, Alt and Super are "capture keys". Never forward them
to the input manager, to avoid inconsistencies between UP and DOWN
events.
2022-01-23 21:16:40 +01:00
Romain Vimont
8c7f0ed5ea Fix warning message
Make the message consistent for HID keyboard and HID mouse.
2022-01-23 21:16:35 +01:00
Romain Vimont
ac038f276e Add missing break statement
This was harmless because this is the last "case" of the switch, but for
consistency, add the missing break.
2022-01-23 15:01:11 +01:00
Romain Vimont
1f65b1bf87 Remove inline hint
There is no reason to request inlining here.
2022-01-23 15:01:11 +01:00
Romain Vimont
d41a46dc95 Handle libusb_get_device_descriptor() error
The function libusb_get_device_descriptor() might return an error.
Handle it.
2022-01-23 12:32:37 +01:00
Romain Vimont
308a1f8192 Simplify error handling in sc_aoa_init()
Use goto to avoid many repetitions.
2022-01-23 12:32:37 +01:00
Romain Vimont
241a587e61 Fix missing HID mouse destructor call
The destructor unregisters the HID mouse, so it was not reported as a
leak, but it must still be called.
2022-01-23 12:32:04 +01:00
Romain Vimont
7e35bfe382 Refactor if-blocks
Group all conditions requiring a controller in a single if-block.
2022-01-23 12:16:24 +01:00
Romain Vimont
855819bbd8 Remove redundant control boolean
The controller is NULL if and only if control is disabled, so an
additional control boolean is redundant.
2022-01-23 12:16:24 +01:00
Romain Vimont
557daf280e Pass NULL controller if control is disabled
If --no-control is requested, then the controller instance is not
initialized. However, its reference was still passed to screen and
input_manager.

Instead, pass NULL if no controller is available.
2022-01-23 12:16:24 +01:00
Romain Vimont
0b8e926330 Do not process finger events if no control
If --no-control is passed, then im->mp is NULL, so processing touches
would crash.
2022-01-23 12:16:24 +01:00
Romain Vimont
0ec3361bc9 Fix crash on --no-control
Relative mouse mode assumed that a mouse processor was always available,
but this is not the case if --no-control is passed.
2022-01-23 12:16:07 +01:00
Romain Vimont
81ff7ebd06 Simplify event loop
Merge single event handling with the event loop function.
2022-01-21 21:52:41 +01:00
Romain Vimont
1ffe312369 Handle file drop from input_manager
A file is pushed (or an apk is installed) to the device on file drop.
This behavior is specific to the screen and its input_manager.
2022-01-21 21:52:41 +01:00
Romain Vimont
ebef027c4f Do not return status for event handling
It is never read. Simplify.
2022-01-21 21:52:41 +01:00
Romain Vimont
8e4e7d42f1 Fix leak on file pusher error
If a file_push request fails, the allocated filename must be freed.
2022-01-21 21:52:41 +01:00
Romain Vimont
b066dc0bbf Rename file_handler to sc_file_pusher
Rename handler to pusher ("handler" is too generic), and add sc_ prefix.
2022-01-21 21:52:41 +01:00
Romain Vimont
262506c733 Limit retry-on-error to IllegalStateException
MediaCodec errors always trigger IllegalStateException or a subtype
(like MediaCodec.CodecException).

In practice, this avoids to retry if the error is caused by an
IOException when writing the video packet to the socket.
2022-01-21 21:52:29 +01:00
Romain Vimont
2eb6fe7d81 Downsize on error only before the first frame
The purpose of automatic downscaling on error is to make mirroring work
by just starting scrcpy without an explicit -m value, even if the
encoder could not encode at the screen definition.

It is only useful when we detect an encoding failure before the first
frame. Downsizing later could be surprising, so disable it.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:44:05 +01:00
Romain Vimont
3a0ba7d0a4 Disable downsizing on error if V4L2 is enabled
V4L2 device is created with the initial device size, it does not support
resizing.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:44:05 +01:00
Romain Vimont
75c5dc6859 Position and size the window on first frame
The optimal initial size was computed from the expected dimensions, sent
immediately by the server before encoding any video frame.

However, the actual frame size may be different, for example when the
device encoder does not support the requested size.

To always handle this case properly, position and size the window only
once the first frame size is known.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:43:46 +01:00
Romain Vimont
fa30f9806a Move "show window" call on first frame
Show the window only after the actual frame size is known (and if no
error has occurred).

This will allow to properly position and size the window when the size
of the first frame is different from the size initially announced by the
server.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:42:39 +01:00
Romain Vimont
4fb61ac83d Fix screen comments
The position fields accept SC_WINDOW_POSITION_UNDEFINED, not the size
fields.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:42:39 +01:00
Romain Vimont
8fa9e6b01a Mention auto-downsize feature in FAQ
PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:42:38 +01:00
Romain Vimont
0ec64baad4 Remove MediaCodec error suggestion fix
Now that scrcpy attempts with a lower definition on any MediaCodec
error (or the user explicitly requests to disable auto-downsizing), the
suggestion is unnecessary.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:41:26 +01:00
Romain Vimont
15bf27afdd Make auto-downsize on error optional
Add --no-downsize-on-error option to disable attempts to use a lower
definition on MediaCodec error.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:36:46 +01:00
Romain Vimont
26b4104844 Downsize on error
Some devices are not able to encode at the device screen definition.

Instead of just failing, try with a lower definition on any MediaCodec
error.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:36:46 +01:00
Romain Vimont
723faa5dee Remember Device parameters
This will allow to reuse them to recreate a ScreenInfo instance in order
to change the maxSize value on MediaCodec error.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:36:46 +01:00
Romain Vimont
162043911e Compute screen size without DisplayInfo instance
Use the actual rotation and size values directly.

This will allow to automatically change the maxSize value on MediaCodec
error.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:36:46 +01:00
Romain Vimont
117fe32626 Fix visibility modifier
Refs b7a06278fe
2022-01-21 18:36:44 +01:00
Thomas Rebele
b7a06278fe Fix NoSuchMethodException for injectInputEvent()
Some devices with modified ROMs expose a different signature for
injectInputEvent().

Fixes #2250 <https://github.com/Genymobile/scrcpy/issues/2250>
PR #2946 <https://github.com/Genymobile/scrcpy/pull/2946>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-01-18 10:15:55 +01:00
Romain Vimont
b3ff1f6b3b Upgrade FFmpeg (5.0) for Windows 64-bit
Use FFmpeg win64 binaries from gyan.dev (referenced from ffmpeg.org):

 - https://www.gyan.dev/ffmpeg/builds/
 - https://ffmpeg.org/download.html#build-windows

Keep the old FFmpeg prebuilt binaries (4.3.1) for win32 builds.

Fixes #1753 <https://github.com/Genymobile/scrcpy/issues/1753>
Refs #1838 <https://github.com/Genymobile/scrcpy/pull/1838>
Refs #2583 <https://github.com/Genymobile/scrcpy/pull/2583>
PR #2952 <https://github.com/Genymobile/scrcpy/pull/2952>

Co-authored-by: Yu-Chen Lin <npes87184@gmail.com>
Co-authored-by: nkh0472 <nkh0472@hotmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-01-18 10:13:41 +01:00
Romain Vimont
a2495c5ef1 Use symlink to simplify Windows ffmpeg dependency
The FFmpeg dependency is downloaded from two separate zipfiles.

Symlink include/ to expose everything from a single directory, to
simplify the meson script.

PR #2952 <https://github.com/Genymobile/scrcpy/pull/2952>
2022-01-18 10:13:38 +01:00
Romain Vimont
37c7827d46 Simplify ffmpeg dependencies Makefile
The fact that the current prebuilt FFmpeg is split into two separate
zipfiles is an implementation detail.

Use a single Makefile recipe for both files.

PR #2952 <https://github.com/Genymobile/scrcpy/pull/2952>
2022-01-18 10:13:18 +01:00
32 changed files with 720 additions and 540 deletions

3
FAQ.md
View File

@@ -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).

View File

@@ -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)
) )

View File

@@ -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).

View File

@@ -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

View File

@@ -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;
} }

View File

@@ -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);
}

View File

@@ -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
View 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
View 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

View File

@@ -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");
} }
} }

View File

@@ -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"

View File

@@ -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;
} }

View File

@@ -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

View File

@@ -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,
}; };

View File

@@ -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;
}; };

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}; };

View File

@@ -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'

View File

@@ -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'

View File

@@ -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 \

View File

@@ -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

View File

@@ -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)/"

View File

@@ -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);

View File

@@ -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;
} }

View File

@@ -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;
}
} }

View File

@@ -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) {

View File

@@ -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

View File

@@ -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();

View File

@@ -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);