Compare commits

...

34 Commits

Author SHA1 Message Date
Simon Chan
266644560d Introduce --keyboard and --mouse
Until now, there was two modes for keyboard and mouse:
 - event injection using the Android system API (default)
 - HID/AOA over USB

For this reason, the options were exposed as simple flags:
 - -K or --hid-keyboard to enable physical keyboard simulation (AOA)
 - -M or --hid-mouse to enable physical mouse simulation (AOA)

Replace them by explicit --keyboard and --mouse options, with 3 possible
values:
 - disabled
 - sdk (default)
 - aoa

This will allow to add a new mode (uhid).

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-01-20 18:48:28 +01:00
Romain Vimont
e572e841a8 Accept disabled keyboard or mouse
The input manager assumed that if a controller was present, then both a
key processor and a mouse processor were present.

Remove this assumption, to support disabling keyboard and mouse
separately. This prepares the introduction of new command line options
--keyboard and --mouse.
2024-01-20 18:36:09 +01:00
Romain Vimont
faf99d4d2f Always pass input manager instance
Some functions in input_manager.c only have access to a sub-object (for
example the controller). For consistency, always pass the whole
input manager instance.

This will allow to add assertions when keyboard and mouse could be
disabled separately.
2024-01-20 18:21:17 +01:00
Romain Vimont
5187f7254e Add another clipboard workaround for IQOO device
Fixes #4589 <https://github.com/Genymobile/scrcpy/issues/4589>
Refs 5ce8672ebc
Refs #4492 <https://github.com/Genymobile/scrcpy/issues/4492>
2024-01-17 10:15:00 +01:00
Romain Vimont
2ad93d1fc0 Fix scrcpy_otg() return value on error
The function now returns an enum scrcpy_exit_code, not a bool.
2024-01-15 22:01:19 +01:00
Romain Vimont
d067a11478 Do not power on if no video
Power on the device on start only if video capture is enabled.

Note that it only impacts display mirroring, since control is completely
disabled if video source is camera.

Refs 110b3a16f6
2024-01-07 21:12:39 +01:00
Romain Vimont
cd4056d0f3 Fix include formatting 2024-01-02 10:22:28 +01:00
Romain Vimont
6a58891e13 Use current time as initial timestamp on error
If the initial timestamp could not be retrieved, use the current time as
returned by System.nanoTime(). In practice, it is the same time base as
AudioRecord timestamps.

Fixes #4536 <https://github.com/Genymobile/scrcpy/issues/4536>
2023-12-16 20:17:33 +01:00
Romain Vimont
ec41896c85 Fix integer overflow for audio packet duration
The result is assigned to a long (64-bit signed integer), but the
intermediate multiplication was stored in an int (32-bit signed
integer).

This value is only used as a fallback when no timestamp could be
retrieved, that's why it did not cause too much harm so far.

Fixes #4536 <https://github.com/Genymobile/scrcpy/issues/4536>
2023-12-16 20:16:31 +01:00
Romain Vimont
4cd61b5a90 Fix checkstyle violation
Reported by checkstyle:

> [ant:checkstyle] [INFO]
> scrcpy/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java:48:
> Line is longer than 150 characters (found 167). [LineLength]
2023-12-16 20:12:58 +01:00
Till Rathmann
d2ed4510a7 Simulate tilt multitouch event by pressing Shift
PR #4529 <https://github.com/Genymobile/scrcpy/pull/4529>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-12-15 22:12:07 +01:00
Harsh Shandilya
604dfd7c6b Fix incorrect compgen usage
PR #4532 <https://github.com/Genymobile/scrcpy/pull/4532>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-12-14 17:08:19 +01:00
Harsh Shandilya
af69689ec1 Fix bash completion syntax
PR #4532 <https://github.com/Genymobile/scrcpy/pull/4532>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-12-14 17:07:20 +01:00
Romain Vimont
cbce42336d Fix manpage syntax
The '-' character must be escaped.

Fixes #4528 <https://github.com/Genymobile/scrcpy/issues/4528>
2023-12-13 13:42:57 +01:00
Romain Vimont
c9a4d2b38f Use up-to-date values on display fold change
When a display is folded or unfolded, the maxSize may have been updated
since the option was passed, and deviceSize must be updated.

Refs #4469 <https://github.com/Genymobile/scrcpy/pull/4469>
2023-12-04 08:50:12 +01:00
Romain Vimont
1beec99f82 Explicitly exit cleanup process
This avoids an internal crash reported in `adb logcat`.

Refs #4456 <https://github.com/Genymobile/scrcpy/pull/4456#issuecomment-1837427802>
2023-12-04 08:45:35 +01:00
Romain Vimont
5ce8672ebc Add clipboard workaround for IQOO device
Fixes #4492 <https://github.com/Genymobile/scrcpy/issues/4492>
2023-12-04 08:43:31 +01:00
Romain Vimont
3001f8a2d5 Adapt AudioRecord workaround to Android 14
Android 14 added a new int parameter "halInputFlags" to an internal
method:
<f6135d75db>

Fixes #4492 <https://github.com/Genymobile/scrcpy/issues/4492>
2023-12-03 18:01:11 +01:00
Romain Vimont
c6ff78f414 Update links to v2.3.1 2023-12-02 12:39:05 +01:00
Romain Vimont
40f2560d98 Bump version to 2.3.1 2023-12-02 12:30:19 +01:00
Romain Vimont
26aa28c998 Merge branch 'master' into release 2023-12-02 12:29:31 +01:00
Romain Vimont
ef79fcbbd2 Fix AV1 demuxing
For AV1, the config packet must not be merged with the next non-config
packet.

This fixes the following error when passing --video-codec=av1:

> INFO: [FFmpeg] libdav1d 1.3.0
> ERROR: [FFmpeg] Unknown OBU type 0 of size 29393
> ERROR: [FFmpeg] Error parsing OBU data
> ERROR: Decoder 'video': could not send video packet: -1094995529

PR #4487 <https://github.com/Genymobile/scrcpy/pull/4487>
2023-12-02 12:20:01 +01:00
Romain Vimont
9497f39fb4 Do not fail if SDL_INIT_VIDEO fails without video
The SDL video subsystem may be initialized so that clipboard
synchronization works even without video playback.

But if the video subsystem initialization fails (e.g. because no video
device is available), consider it as an error only if video playback is
enabled.

Refs 5e59ed3135
Fixes #4477 <https://github.com/Genymobile/scrcpy/issues/4477>
2023-11-29 12:16:05 +01:00
Romain Vimont
bf056b1fee Do not initialize SDL video when not necessary
The SDL video subsystem is required for video playback and clipboard
synchronization.

If neither is used, it is not necessary to initialize it.

Refs 5e59ed3135
Refs 110b3a16f6
Refs #4418 <https://github.com/Genymobile/scrcpy/issues/4418>
Refs #4477 <https://github.com/Genymobile/scrcpy/issues/4477>
2023-11-29 12:14:07 +01:00
Johannes Neyer
bd9292931e Mention exclusive_caps mode in v4l2 documentation
PR #4435 <https://github.com/Genymobile/scrcpy/pull/4435>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-11-28 08:32:28 +01:00
Romain Vimont
140a49b8be Add workaround for Samsung devices issues
On some Samsung devices, DisplayManagerGlobal.getDisplayInfoLocked()
calls ActivityThread.currentActivityThread().getConfiguration(), which
requires a non-null ConfigurationController.

Fixes <https://github.com/Genymobile/scrcpy/issues/4467>
2023-11-27 09:29:06 +01:00
Romain Vimont
4135c411af Fix compilation error
Fix the following warning/error:

    ../app/src/cli.c:2158:17: warning: a label can only be part of a
    statement and a declaration is not a statement [-Wpedantic]

With some compilers, this is an error rather than a pedantic warning.

Refs <https://github.com/Genymobile/scrcpy/issues/2256#issuecomment-1467008307>
2023-11-25 23:56:46 +01:00
Romain Vimont
5e061636f6 Update links to v2.3 2023-11-25 22:15:07 +01:00
Romain Vimont
5f3fb843f5 Bump version to 2.3
The previous version bump to 2.2 was incorrect, it was updated by:

    ./bump_version v2.2

instead of:

    ./bump_version 2.2

Correctly bump to version 2.3.

Refs #4433 <https://github.com/Genymobile/scrcpy/issues/4433#issuecomment-1816830875>
2023-11-25 21:40:27 +01:00
Romain Vimont
ce8126f322 Merge branch 'master' into release 2023-11-25 21:37:37 +01:00
Romain Vimont
d037b02cc2 Fix scrcpy-console.desktop
The argument passed to scrcpy was not applied, the full command must be
passed as a single argument.

PR #4448 <https://github.com/Genymobile/scrcpy/pull/4448>
2023-11-25 21:35:04 +01:00
Kid
89761213c3 Do not quote $SHELL in .desktop files
This does not work properly on some desktop environments (KDE), and
$SHELL is unlikely to require quoting.

Fixes #4367 <https://github.com/Genymobile/scrcpy/issues/4367>
PR #4448 <https://github.com/Genymobile/scrcpy/pull/4448>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-11-25 21:28:43 +01:00
Kid
8db4e78b34 Fix Linux desktop files
There were too many backslashes in the Exec line.

Fixes #4367 <https://github.com/Genymobile/scrcpy/issues/4367>
PR #4448 <https://github.com/Genymobile/scrcpy/pull/4448>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-11-25 21:28:43 +01:00
Romain Vimont
25e33566f5 Mention turning off audio in camera documentation 2023-11-21 08:46:38 +01:00
31 changed files with 536 additions and 246 deletions

View File

@@ -1,4 +1,4 @@
# scrcpy (v2.2)
# scrcpy (v2.3.1)
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />

View File

@@ -27,8 +27,8 @@ _scrcpy() {
--force-adb-forward
--forward-all-clicks
-h --help
--keyboard
--kill-adb-on-close
-K --hid-keyboard
--legacy-paste
--list-camera-sizes
--list-cameras
@@ -39,6 +39,7 @@ _scrcpy() {
-m --max-size=
-M --hid-mouse
--max-fps=
--mouse=
-n --no-control
-N --no-playback
--no-audio
@@ -115,13 +116,20 @@ _scrcpy() {
COMPREPLY=($(compgen -W 'front back external' -- "$cur"))
return
;;
--orientation
--display-orientation)
COMPREPLY=($(compgen -> '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
--keyboard)
COMPREPLY=($(compgen -W 'disabled sdk aoa' -- "$cur"))
return
;;
--mouse)
COMPREPLY=($(compgen -W 'disabled sdk aoa' -- "$cur"))
return
;;
--orientation|--display-orientation)
COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
return
;;
--record-orientation)
COMPREPLY=($(compgen -> '0 90 180 270' -- "$cur"))
COMPREPLY=($(compgen -W '0 90 180 270' -- "$cur"))
return
;;
--lock-video-orientation)

View File

@@ -5,7 +5,7 @@ Comment=Display and control your Android device
# For some users, the PATH or ADB environment variables are set from the shell
# startup file, like .bashrc or .zshrc… Run an interactive shell to get
# environment correctly initialized.
Exec=/bin/sh -c "\"\\$SHELL\" -i -c scrcpy --pause-on-exit=if-error"
Exec=/bin/sh -c "\\$SHELL -i -c 'scrcpy --pause-on-exit=if-error'"
Icon=scrcpy
Terminal=true
Type=Application

View File

@@ -5,7 +5,7 @@ Comment=Display and control your Android device
# For some users, the PATH or ADB environment variables are set from the shell
# startup file, like .bashrc or .zshrc… Run an interactive shell to get
# environment correctly initialized.
Exec=/bin/sh -c "\"\\$SHELL\" -i -c scrcpy"
Exec=/bin/sh -c "\\$SHELL -i -c scrcpy"
Icon=scrcpy
Terminal=false
Type=Application

View File

@@ -34,8 +34,8 @@ arguments=(
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]'
'--forward-all-clicks[Forward clicks to device]'
{-h,--help}'[Print the help]'
'--keyboard[Set the keyboard input mode]:mode:(disabled sdk aoa)'
'--kill-adb-on-close[Kill adb when scrcpy terminates]'
{-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]'
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
'--list-camera-sizes[List the valid camera capture sizes]'
'--list-cameras[List cameras available on the device]'
@@ -45,6 +45,7 @@ arguments=(
{-m,--max-size=}'[Limit both the width and height of the video to value]'
{-M,--hid-mouse}'[Simulate a physical mouse by using HID over AOAv2]'
'--max-fps=[Limit the frame rate of screen capture]'
'--mouse[Set the mouse input mode]:mode:(disabled sdk aoa)'
{-n,--no-control}'[Disable device control \(mirror the device in read only\)]'
{-N,--no-playback}'[Disable video and audio playback]'
'--no-audio[Disable audio forwarding]'

View File

@@ -13,7 +13,7 @@ BEGIN
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
VALUE "OriginalFilename", "scrcpy.exe"
VALUE "ProductName", "scrcpy"
VALUE "ProductVersion", "v2.2"
VALUE "ProductVersion", "2.3.1"
END
END
BLOCK "VarFileInfo"

View File

@@ -124,7 +124,7 @@ Use USB device (if there is exactly one, like adb -d).
Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR).
.TP
.BI "\-\-disable-screensaver"
.BI "\-\-disable\-screensaver"
Disable screensaver while scrcpy is running.
.TP
@@ -172,24 +172,26 @@ By default, right-click triggers BACK (or POWER on) and middle-click triggers HO
Print this help.
.TP
.B \-\-kill\-adb\-on\-close
Kill adb when scrcpy terminates.
.BI "\-\-keyboard " mode
Select how to send keyboard inputs to the device.
.TP
.B \-K, \-\-hid\-keyboard
Simulate a physical keyboard by using HID over AOAv2.
Possible values are "disabled", "sdk" and "aoa":
This provides a better experience for IME users, and allows to generate non-ASCII characters, contrary to the default injection method.
- "disabled" does not send keyboard inputs to the device.
- "sdk" uses the Android system API to deliver keyboard events to applications.
- "aoa" simulates a physical keyboard using the AOAv2 protocol. It may only work over USB.
It may only work over USB.
The keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly:
For "aoa", the keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly:
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
However, the option is only available when the HID keyboard is enabled (or a physical keyboard is connected).
This option is only available when the HID keyboard is enabled (or a physical keyboard is connected).
Also see \fB\-\-hid\-mouse\fR.
Also see \fB\-\-mouse\fR.
.TP
.B \-\-kill\-adb\-on\-close
Kill adb when scrcpy terminates.
.TP
.B \-\-legacy\-paste
@@ -230,20 +232,25 @@ Limit both the width and height of the video to \fIvalue\fR. The other dimension
Default is 0 (unlimited).
.TP
.B \-M, \-\-hid\-mouse
Simulate a physical mouse by using HID over AOAv2.
.BI "\-\-max\-fps " value
Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions).
In this mode, the computer mouse is captured to control the device directly (relative mouse mode).
.TP
.BI "\-\-mouse " mode
Select how to send mouse inputs to the device.
Possible values are "disabled", "sdk" and "aoa":
- "disabled" does not send mouse inputs to the device.
- "sdk" uses the Android system API to deliver mouse events to applications.
- "aoa" simulates a physical mouse using the AOAv2 protocol. It may only work over USB.
In "aoa" mode, the computer mouse is captured to control the device directly (relative mouse mode).
LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer.
It may only work over USB.
Also see \fB\-\-keyboard\fR.
Also see \fB\-\-hid\-keyboard\fR.
.TP
.BI "\-\-max\-fps " value
Limit the framerate of screen capture (officially supported since Android 10, but may work on earlier versions).
.TP
.B \-n, \-\-no\-control
@@ -642,7 +649,11 @@ Enable/disable FPS counter (print frames/second in logs)
.TP
.B Ctrl+click-and-move
Pinch-to-zoom from the center of the screen
Pinch-to-zoom and rotate from the center of the screen
.TP
.B Shift+click-and-move
Tilt (slide vertically with two fingers)
.TP
.B Drag & drop APK file

View File

@@ -4,16 +4,16 @@
#include "common.h"
#include <stdbool.h>
#include "trait/frame_sink.h"
#include <util/audiobuf.h>
#include <util/average.h>
#include <util/thread.h>
#include <util/tick.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <SDL2/SDL.h>
#include "trait/frame_sink.h"
#include "util/audiobuf.h"
#include "util/average.h"
#include "util/thread.h"
#include "util/tick.h"
struct sc_audio_player {
struct sc_frame_sink frame_sink;

View File

@@ -93,6 +93,8 @@ enum {
OPT_DISPLAY_ORIENTATION,
OPT_RECORD_ORIENTATION,
OPT_ORIENTATION,
OPT_KEYBOARD,
OPT_MOUSE,
};
struct sc_option {
@@ -358,27 +360,35 @@ static const struct sc_option options[] = {
.longopt = "help",
.text = "Print this help.",
},
{
.longopt_id = OPT_KEYBOARD,
.longopt = "keyboard",
.argdesc = "mode",
.text = "Select how to send keyboard inputs to the device.\n"
"Possible values are \"disabled\", \"sdk\" and \"aoa\".\n"
"\"disabled\" does not send keyboard inputs to the device.\n"
"\"sdk\" uses the Android system API to deliver keyboard\n"
"events to applications.\n"
"\"aoa\" simulates a physical keyboard using the AOAv2\n"
"protocol. It may only work over USB.\n"
"For \"aoa\", the keyboard layout must be configured (once and "
"for all) on the device, via Settings -> System -> Languages "
"and input -> Physical keyboard. This settings page can be "
"started directly: `adb shell am start -a "
"android.settings.HARD_KEYBOARD_SETTINGS`.\n"
"This option is only available when the HID keyboard is "
"enabled (or a physical keyboard is connected).\n"
"Also see --mouse.",
},
{
.longopt_id = OPT_KILL_ADB_ON_CLOSE,
.longopt = "kill-adb-on-close",
.text = "Kill adb when scrcpy terminates.",
},
{
// deprecated
.shortopt = 'K',
.longopt = "hid-keyboard",
.text = "Simulate a physical keyboard by using HID over AOAv2.\n"
"It provides a better experience for IME users, and allows to "
"generate non-ASCII characters, contrary to the default "
"injection method.\n"
"It may only work over USB.\n"
"The keyboard layout must be configured (once and for all) on "
"the device, via Settings -> System -> Languages and input -> "
"Physical keyboard. This settings page can be started "
"directly: `adb shell am start -a "
"android.settings.HARD_KEYBOARD_SETTINGS`.\n"
"However, the option is only available when the HID keyboard "
"is enabled (or a physical keyboard is connected).\n"
"Also see --hid-mouse.",
},
{
.longopt_id = OPT_LEGACY_PASTE,
@@ -432,15 +442,9 @@ static const struct sc_option options[] = {
"Default is 0 (unlimited).",
},
{
// deprecated
.shortopt = 'M',
.longopt = "hid-mouse",
.text = "Simulate a physical mouse by using HID over AOAv2.\n"
"In this mode, the computer mouse is captured to control the "
"device directly (relative mouse mode).\n"
"LAlt, LSuper or RSuper toggle the capture mode, to give "
"control of the mouse back to the computer.\n"
"It may only work over USB.\n"
"Also see --hid-keyboard.",
},
{
.longopt_id = OPT_MAX_FPS,
@@ -449,6 +453,23 @@ static const struct sc_option options[] = {
.text = "Limit the frame rate of screen capture (officially supported "
"since Android 10, but may work on earlier versions).",
},
{
.longopt_id = OPT_MOUSE,
.longopt = "mouse",
.argdesc = "mode",
.text = "Select how to send mouse inputs to the device.\n"
"Possible values are \"disabled\", \"sdk\" and \"aoa\".\n"
"\"disabled\" does not send mouse inputs to the device.\n"
"\"sdk\" uses the Android system API to deliver mouse\n"
"events to applications.\n"
"\"aoa\" simulates a physical mouse using the AOAv2 protocol\n"
"It may only work over USB.\n"
"In \"aoa\" mode, the computer mouse is captured to control "
"the device directly (relative mouse mode).\n"
"LAlt, LSuper or RSuper toggle the capture mode, to give "
"control of the mouse back to the computer.\n"
"Also see --keyboard.",
},
{
.shortopt = 'n',
.longopt = "no-control",
@@ -543,10 +564,10 @@ static const struct sc_option options[] = {
"mirroring is disabled.\n"
"LAlt, LSuper or RSuper toggle the mouse capture mode, to give "
"control of the mouse back to the computer.\n"
"If any of --hid-keyboard or --hid-mouse is set, only enable "
"keyboard or mouse respectively, otherwise enable both.\n"
"Keyboard and mouse may be disabled separately using\n"
"--keyboard=disabled and --mouse=disabled.\n"
"It may only work over USB.\n"
"See --hid-keyboard and --hid-mouse.",
"See --keyboard and --mouse.",
},
{
.shortopt = 'p',
@@ -947,7 +968,11 @@ static const struct sc_shortcut shortcuts[] = {
},
{
.shortcuts = { "Ctrl+click-and-move" },
.text = "Pinch-to-zoom from the center of the screen",
.text = "Pinch-to-zoom and rotate from the center of the screen",
},
{
.shortcuts = { "Shift+click-and-move" },
.text = "Tilt (slide vertically with two fingers)",
},
{
.shortcuts = { "Drag & drop APK file" },
@@ -1898,6 +1923,58 @@ parse_camera_fps(const char *s, uint16_t *camera_fps) {
return true;
}
static bool
parse_keyboard(const char *optarg, enum sc_keyboard_input_mode *mode) {
if (!strcmp(optarg, "disabled")) {
*mode = SC_KEYBOARD_INPUT_MODE_DISABLED;
return true;
}
if (!strcmp(optarg, "sdk")) {
*mode = SC_KEYBOARD_INPUT_MODE_SDK;
return true;
}
if (!strcmp(optarg, "aoa")) {
#ifdef HAVE_USB
*mode = SC_KEYBOARD_INPUT_MODE_AOA;
return true;
#else
LOGE("--keyboard=aoa is disabled.");
return false;
#endif
}
LOGE("Unsupported keyboard: %s (expected disabled, sdk or aoa)", optarg);
return false;
}
static bool
parse_mouse(const char *optarg, enum sc_mouse_input_mode *mode) {
if (!strcmp(optarg, "disabled")) {
*mode = SC_MOUSE_INPUT_MODE_DISABLED;
return true;
}
if (!strcmp(optarg, "sdk")) {
*mode = SC_MOUSE_INPUT_MODE_SDK;
return true;
}
if (!strcmp(optarg, "aoa")) {
#ifdef HAVE_USB
*mode = SC_MOUSE_INPUT_MODE_AOA;
return true;
#else
LOGE("--mouse=aoa is disabled.");
return false;
#endif
}
LOGE("Unsupported mouse: %s (expected disabled, sdk or aoa)", optarg);
return false;
}
static bool
parse_time_limit(const char *s, sc_tick *tick) {
long value;
@@ -1987,12 +2064,19 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
break;
case 'K':
#ifdef HAVE_USB
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID;
LOGW("-K/--hid-keyboard is deprecated, use --keyboard=aoa "
"instead.");
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AOA;
break;
#else
LOGE("HID over AOA (-K/--hid-keyboard) is disabled.");
return false;
#endif
case OPT_KEYBOARD:
if (!parse_keyboard(optarg, &opts->keyboard_input_mode)) {
return false;
}
break;
case OPT_MAX_FPS:
if (!parse_max_fps(optarg, &opts->max_fps)) {
return false;
@@ -2005,12 +2089,18 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
break;
case 'M':
#ifdef HAVE_USB
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID;
LOGW("-M/--hid-mouse is deprecated, use --mouse=aoa instead.");
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA;
break;
#else
LOGE("HID over AOA (-M/--hid-mouse) is disabled.");
return false;
#endif
case OPT_MOUSE:
if (!parse_mouse(optarg, &opts->mouse_input_mode)) {
return false;
}
break;
case OPT_LOCK_VIDEO_ORIENTATION:
if (!parse_lock_video_orientation(optarg,
&opts->lock_video_orientation)) {
@@ -2154,7 +2244,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false;
}
break;
case OPT_ORIENTATION:
case OPT_ORIENTATION: {
enum sc_orientation orientation;
if (!parse_orientation(optarg, &orientation)) {
return false;
@@ -2162,6 +2252,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->display_orientation = orientation;
opts->record_orientation = orientation;
break;
}
case OPT_RENDER_DRIVER:
opts->render_driver = optarg;
break;
@@ -2393,6 +2484,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
if (!opts->video) {
opts->video_playback = false;
// Do not power on the device on start if video capture is disabled
opts->power_on = false;
}
if (!opts->audio) {
@@ -2454,6 +2547,31 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
#endif
if (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) {
opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA
: SC_KEYBOARD_INPUT_MODE_SDK;
}
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) {
opts->mouse_input_mode = otg ? SC_MOUSE_INPUT_MODE_AOA
: SC_MOUSE_INPUT_MODE_SDK;
}
if (otg) {
enum sc_keyboard_input_mode kmode = opts->keyboard_input_mode;
if (kmode != SC_KEYBOARD_INPUT_MODE_AOA
&& kmode != SC_KEYBOARD_INPUT_MODE_DISABLED) {
LOGE("In OTG mode, --keyboard only supports aoa or disabled.");
return false;
}
enum sc_mouse_input_mode mmode = opts->mouse_input_mode;
if (mmode != SC_MOUSE_INPUT_MODE_AOA
&& mmode != SC_MOUSE_INPUT_MODE_DISABLED) {
LOGE("In OTG mode, --mouse only supports aoa or disabled.");
return false;
}
}
if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) {
LOGI("Tunnel host/port is set, "
"--force-adb-forward automatically enabled.");
@@ -2614,12 +2732,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
# ifdef _WIN32
if (!otg && (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID
|| opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID)) {
if (!otg && (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA
|| opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA)) {
LOGE("On Windows, it is not possible to open a USB device already open "
"by another process (like adb).");
LOGE("Therefore, -K/--hid-keyboard and -M/--hid-mouse may only work in "
"OTG mode (--otg).");
LOGE("Therefore, --keyboard=aoa and --mouse=aoa may only work in OTG"
"mode (--otg).");
return false;
}
# endif

View File

@@ -227,8 +227,9 @@ run_demuxer(void *data) {
}
// Config packets must be merged with the next non-config packet only for
// video streams
bool must_merge_config_packet = codec->type == AVMEDIA_TYPE_VIDEO;
// H.26x
bool must_merge_config_packet = raw_codec_id == SC_CODEC_ID_H264
|| raw_codec_id == SC_CODEC_ID_H265;
struct sc_packet_merger merger;

View File

@@ -52,8 +52,11 @@ 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->controller || (params->kp && params->kp->ops));
assert(!params->controller || (params->mp && params->mp->ops));
// A key/mouse processor may not be present if there is no controller
assert((!params->kp && !params->mp) || params->controller);
// A processor must have ops initialized
assert(!params->kp || params->kp->ops);
assert(!params->mp || params->mp->ops);
im->controller = params->controller;
im->fp = params->fp;
@@ -76,6 +79,8 @@ sc_input_manager_init(struct sc_input_manager *im,
im->sdl_shortcut_mods.count = shortcut_mods->count;
im->vfinger_down = false;
im->vfinger_invert_x = false;
im->vfinger_invert_y = false;
im->last_keycode = SDLK_UNKNOWN;
im->last_mod = 0;
@@ -85,8 +90,10 @@ sc_input_manager_init(struct sc_input_manager *im,
}
static void
send_keycode(struct sc_controller *controller, enum android_keycode keycode,
send_keycode(struct sc_input_manager *im, enum android_keycode keycode,
enum sc_action action, const char *name) {
assert(im->controller && im->kp);
// send DOWN event
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE;
@@ -97,100 +104,109 @@ send_keycode(struct sc_controller *controller, enum android_keycode keycode,
msg.inject_keycode.metastate = 0;
msg.inject_keycode.repeat = 0;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject %s'", name);
}
}
static inline void
action_home(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_HOME, action, "HOME");
action_home(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_HOME, action, "HOME");
}
static inline void
action_back(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_BACK, action, "BACK");
action_back(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_BACK, action, "BACK");
}
static inline void
action_app_switch(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_APP_SWITCH, action, "APP_SWITCH");
action_app_switch(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_APP_SWITCH, action, "APP_SWITCH");
}
static inline void
action_power(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_POWER, action, "POWER");
action_power(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_POWER, action, "POWER");
}
static inline void
action_volume_up(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_VOLUME_UP, action, "VOLUME_UP");
action_volume_up(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_VOLUME_UP, action, "VOLUME_UP");
}
static inline void
action_volume_down(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, action, "VOLUME_DOWN");
action_volume_down(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_VOLUME_DOWN, action, "VOLUME_DOWN");
}
static inline void
action_menu(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_MENU, action, "MENU");
action_menu(struct sc_input_manager *im, enum sc_action action) {
send_keycode(im, AKEYCODE_MENU, action, "MENU");
}
// turn the screen on if it was off, press BACK otherwise
// If the screen is off, it is turned on only on ACTION_DOWN
static void
press_back_or_turn_screen_on(struct sc_controller *controller,
press_back_or_turn_screen_on(struct sc_input_manager *im,
enum sc_action action) {
assert(im->controller && im->kp);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
msg.back_or_screen_on.action = action == SC_ACTION_DOWN
? AKEY_EVENT_ACTION_DOWN
: AKEY_EVENT_ACTION_UP;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'press back or turn screen on'");
}
}
static void
expand_notification_panel(struct sc_controller *controller) {
expand_notification_panel(struct sc_input_manager *im) {
assert(im->controller);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'expand notification panel'");
}
}
static void
expand_settings_panel(struct sc_controller *controller) {
expand_settings_panel(struct sc_input_manager *im) {
assert(im->controller);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'expand settings panel'");
}
}
static void
collapse_panels(struct sc_controller *controller) {
collapse_panels(struct sc_input_manager *im) {
assert(im->controller);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'collapse notification panel'");
}
}
static bool
get_device_clipboard(struct sc_controller *controller,
enum sc_copy_key copy_key) {
get_device_clipboard(struct sc_input_manager *im, enum sc_copy_key copy_key) {
assert(im->controller && im->kp);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_GET_CLIPBOARD;
msg.get_clipboard.copy_key = copy_key;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'get device clipboard'");
return false;
}
@@ -199,8 +215,10 @@ get_device_clipboard(struct sc_controller *controller,
}
static bool
set_device_clipboard(struct sc_controller *controller, bool paste,
set_device_clipboard(struct sc_input_manager *im, bool paste,
uint64_t sequence) {
assert(im->controller && im->kp);
char *text = SDL_GetClipboardText();
if (!text) {
LOGW("Could not get clipboard text: %s", SDL_GetError());
@@ -220,7 +238,7 @@ set_device_clipboard(struct sc_controller *controller, bool paste,
msg.set_clipboard.text = text_dup;
msg.set_clipboard.paste = paste;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
free(text_dup);
LOGW("Could not request 'set device clipboard'");
return false;
@@ -230,19 +248,23 @@ set_device_clipboard(struct sc_controller *controller, bool paste,
}
static void
set_screen_power_mode(struct sc_controller *controller,
set_screen_power_mode(struct sc_input_manager *im,
enum sc_screen_power_mode mode) {
assert(im->controller);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = mode;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'set screen power mode'");
}
}
static void
switch_fps_counter_state(struct sc_fps_counter *fps_counter) {
switch_fps_counter_state(struct sc_input_manager *im) {
struct sc_fps_counter *fps_counter = &im->screen->fps_counter;
// the started state can only be written from the current thread, so there
// is no ToCToU issue
if (sc_fps_counter_is_started(fps_counter)) {
@@ -254,7 +276,9 @@ switch_fps_counter_state(struct sc_fps_counter *fps_counter) {
}
static void
clipboard_paste(struct sc_controller *controller) {
clipboard_paste(struct sc_input_manager *im) {
assert(im->controller && im->kp);
char *text = SDL_GetClipboardText();
if (!text) {
LOGW("Could not get clipboard text: %s", SDL_GetError());
@@ -276,25 +300,28 @@ clipboard_paste(struct sc_controller *controller) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = text_dup;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
free(text_dup);
LOGW("Could not request 'paste clipboard'");
}
}
static void
rotate_device(struct sc_controller *controller) {
rotate_device(struct sc_input_manager *im) {
assert(im->controller);
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_ROTATE_DEVICE;
if (!sc_controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request device rotation");
}
}
static void
apply_orientation_transform(struct sc_screen *screen,
apply_orientation_transform(struct sc_input_manager *im,
enum sc_orientation transform) {
struct sc_screen *screen = im->screen;
enum sc_orientation new_orientation =
sc_orientation_apply(screen->orientation, transform);
sc_screen_set_orientation(screen, new_orientation);
@@ -347,9 +374,14 @@ simulate_virtual_finger(struct sc_input_manager *im,
}
static struct sc_point
inverse_point(struct sc_point point, struct sc_size size) {
point.x = size.width - point.x;
point.y = size.height - point.y;
inverse_point(struct sc_point point, struct sc_size size,
bool invert_x, bool invert_y) {
if (invert_x) {
point.x = size.width - point.x;
}
if (invert_y) {
point.y = size.height - point.y;
}
return point;
}
@@ -357,7 +389,7 @@ static void
sc_input_manager_process_key(struct sc_input_manager *im,
const SDL_KeyboardEvent *event) {
// controller is NULL if --no-control is requested
struct sc_controller *controller = im->controller;
bool control = im->controller;
SDL_Keycode keycode = event->keysym.sym;
uint16_t mod = event->keysym.mod;
@@ -383,68 +415,68 @@ 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 (controller && !shift && !repeat) {
action_home(controller, action);
if (im->kp && !shift && !repeat) {
action_home(im, action);
}
return;
case SDLK_b: // fall-through
case SDLK_BACKSPACE:
if (controller && !shift && !repeat) {
action_back(controller, action);
if (im->kp && !shift && !repeat) {
action_back(im, action);
}
return;
case SDLK_s:
if (controller && !shift && !repeat) {
action_app_switch(controller, action);
if (im->kp && !shift && !repeat) {
action_app_switch(im, action);
}
return;
case SDLK_m:
if (controller && !shift && !repeat) {
action_menu(controller, action);
if (im->kp && !shift && !repeat) {
action_menu(im, action);
}
return;
case SDLK_p:
if (controller && !shift && !repeat) {
action_power(controller, action);
if (im->kp && !shift && !repeat) {
action_power(im, action);
}
return;
case SDLK_o:
if (controller && !repeat && down) {
if (control && !repeat && down) {
enum sc_screen_power_mode mode = shift
? SC_SCREEN_POWER_MODE_NORMAL
: SC_SCREEN_POWER_MODE_OFF;
set_screen_power_mode(controller, mode);
set_screen_power_mode(im, mode);
}
return;
case SDLK_DOWN:
if (shift) {
if (!repeat & down) {
apply_orientation_transform(im->screen,
apply_orientation_transform(im,
SC_ORIENTATION_FLIP_180);
}
} else if (controller) {
} else if (im->kp) {
// forward repeated events
action_volume_down(controller, action);
action_volume_down(im, action);
}
return;
case SDLK_UP:
if (shift) {
if (!repeat & down) {
apply_orientation_transform(im->screen,
apply_orientation_transform(im,
SC_ORIENTATION_FLIP_180);
}
} else if (controller) {
} else if (im->kp) {
// forward repeated events
action_volume_up(controller, action);
action_volume_up(im, action);
}
return;
case SDLK_LEFT:
if (!repeat && down) {
if (shift) {
apply_orientation_transform(im->screen,
apply_orientation_transform(im,
SC_ORIENTATION_FLIP_0);
} else {
apply_orientation_transform(im->screen,
apply_orientation_transform(im,
SC_ORIENTATION_270);
}
}
@@ -452,34 +484,33 @@ sc_input_manager_process_key(struct sc_input_manager *im,
case SDLK_RIGHT:
if (!repeat && down) {
if (shift) {
apply_orientation_transform(im->screen,
apply_orientation_transform(im,
SC_ORIENTATION_FLIP_0);
} else {
apply_orientation_transform(im->screen,
apply_orientation_transform(im,
SC_ORIENTATION_90);
}
}
return;
case SDLK_c:
if (controller && !shift && !repeat && down) {
get_device_clipboard(controller, SC_COPY_KEY_COPY);
if (im->kp && !shift && !repeat && down) {
get_device_clipboard(im, SC_COPY_KEY_COPY);
}
return;
case SDLK_x:
if (controller && !shift && !repeat && down) {
get_device_clipboard(controller, SC_COPY_KEY_CUT);
if (im->kp && !shift && !repeat && down) {
get_device_clipboard(im, SC_COPY_KEY_CUT);
}
return;
case SDLK_v:
if (controller && !repeat && down) {
if (im->kp && !repeat && down) {
if (shift || im->legacy_paste) {
// inject the text as input events
clipboard_paste(controller);
clipboard_paste(im);
} else {
// store the text in the device clipboard and paste,
// without requesting an acknowledgment
set_device_clipboard(controller, true,
SC_SEQUENCE_INVALID);
set_device_clipboard(im, true, SC_SEQUENCE_INVALID);
}
}
return;
@@ -500,23 +531,23 @@ sc_input_manager_process_key(struct sc_input_manager *im,
return;
case SDLK_i:
if (!shift && !repeat && down) {
switch_fps_counter_state(&im->screen->fps_counter);
switch_fps_counter_state(im);
}
return;
case SDLK_n:
if (controller && !repeat && down) {
if (control && !repeat && down) {
if (shift) {
collapse_panels(controller);
collapse_panels(im);
} else if (im->key_repeat == 0) {
expand_notification_panel(controller);
expand_notification_panel(im);
} else {
expand_settings_panel(controller);
expand_settings_panel(im);
}
}
return;
case SDLK_r:
if (controller && !shift && !repeat && down) {
rotate_device(controller);
if (control && !shift && !repeat && down) {
rotate_device(im);
}
return;
}
@@ -524,7 +555,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
return;
}
if (!controller) {
if (!im->kp) {
return;
}
@@ -533,7 +564,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
if (im->clipboard_autosync && is_ctrl_v) {
if (im->legacy_paste) {
// inject the text as input events
clipboard_paste(controller);
clipboard_paste(im);
return;
}
@@ -543,7 +574,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
// Synchronize the computer clipboard to the device clipboard before
// sending Ctrl+v, to allow seamless copy-paste.
bool ok = set_device_clipboard(controller, false, sequence);
bool ok = set_device_clipboard(im, false, sequence);
if (!ok) {
LOGW("Clipboard could not be synchronized, Ctrl+v not injected");
return;
@@ -605,7 +636,9 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
struct sc_point mouse =
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size,
im->vfinger_invert_x,
im->vfinger_invert_y);
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
}
}
@@ -643,7 +676,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) {
struct sc_controller *controller = im->controller;
bool control = im->controller;
if (event->which == SDL_TOUCH_MOUSEID) {
// simulated from touch events, so it's a duplicate
@@ -652,27 +685,27 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
bool down = event->type == SDL_MOUSEBUTTONDOWN;
if (!im->forward_all_clicks) {
if (controller) {
if (control) {
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
if (event->button == SDL_BUTTON_X1) {
action_app_switch(controller, action);
if (im->kp && event->button == SDL_BUTTON_X1) {
action_app_switch(im, action);
return;
}
if (event->button == SDL_BUTTON_X2 && down) {
if (event->clicks < 2) {
expand_notification_panel(controller);
expand_notification_panel(im);
} else {
expand_settings_panel(controller);
expand_settings_panel(im);
}
return;
}
if (event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(controller, action);
if (im->kp && event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(im, action);
return;
}
if (event->button == SDL_BUTTON_MIDDLE) {
action_home(controller, action);
if (im->kp && event->button == SDL_BUTTON_MIDDLE) {
action_home(im, action);
return;
}
}
@@ -695,7 +728,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
// otherwise, send the click event to the device
}
if (!controller) {
if (!im->mp) {
return;
}
@@ -726,7 +759,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
return;
}
// Pinch-to-zoom simulation.
// Pinch-to-zoom, rotate and tilt simulation.
//
// If Ctrl is hold when the left-click button is pressed, then
// pinch-to-zoom mode is enabled: on every mouse event until the left-click
@@ -735,14 +768,29 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
//
// In other words, the center of the rotation/scaling is the center of the
// screen.
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
//
// To simulate a tilt gesture (a vertical slide with two fingers), Shift
// can be used instead of Ctrl. The "virtual finger" has a position
// inverted with respect to the vertical axis of symmetry in the middle of
// the screen.
const SDL_Keymod keymod = SDL_GetModState();
const bool ctrl_pressed = keymod & KMOD_CTRL;
const bool shift_pressed = keymod & KMOD_SHIFT;
if (event->button == SDL_BUTTON_LEFT &&
((down && !im->vfinger_down && CTRL_PRESSED) ||
((down && !im->vfinger_down &&
((ctrl_pressed && !shift_pressed) ||
(!ctrl_pressed && shift_pressed))) ||
(!down && im->vfinger_down))) {
struct sc_point mouse =
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
if (down) {
im->vfinger_invert_x = ctrl_pressed || shift_pressed;
im->vfinger_invert_y = ctrl_pressed;
}
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size,
im->vfinger_invert_x,
im->vfinger_invert_y);
enum android_motionevent_action action = down
? AMOTION_EVENT_ACTION_DOWN
: AMOTION_EVENT_ACTION_UP;
@@ -820,7 +868,7 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
bool control = im->controller;
switch (event->type) {
case SDL_TEXTINPUT:
if (!control) {
if (!im->kp) {
break;
}
sc_input_manager_process_text_input(im, &event->text);
@@ -832,13 +880,13 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
sc_input_manager_process_key(im, &event->key);
break;
case SDL_MOUSEMOTION:
if (!control) {
if (!im->mp) {
break;
}
sc_input_manager_process_mouse_motion(im, &event->motion);
break;
case SDL_MOUSEWHEEL:
if (!control) {
if (!im->mp) {
break;
}
sc_input_manager_process_mouse_wheel(im, &event->wheel);
@@ -852,7 +900,7 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
if (!control) {
if (!im->mp) {
break;
}
sc_input_manager_process_touch(im, &event->tfinger);

View File

@@ -32,6 +32,8 @@ struct sc_input_manager {
} sdl_shortcut_mods;
bool vfinger_down;
bool vfinger_invert_x;
bool vfinger_invert_y;
// Tracks the number of identical consecutive shortcut key down events.
// Not to be confused with event->repeat, which counts the number of

View File

@@ -21,8 +21,8 @@ const struct scrcpy_options scrcpy_options_default = {
.video_source = SC_VIDEO_SOURCE_DISPLAY,
.audio_source = SC_AUDIO_SOURCE_AUTO,
.record_format = SC_RECORD_FORMAT_AUTO,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT,
.mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO,
.mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO,
.camera_facing = SC_CAMERA_FACING_ANY,
.port_range = {
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,

View File

@@ -140,13 +140,17 @@ enum sc_lock_video_orientation {
};
enum sc_keyboard_input_mode {
SC_KEYBOARD_INPUT_MODE_INJECT,
SC_KEYBOARD_INPUT_MODE_HID,
SC_KEYBOARD_INPUT_MODE_AUTO,
SC_KEYBOARD_INPUT_MODE_DISABLED,
SC_KEYBOARD_INPUT_MODE_SDK,
SC_KEYBOARD_INPUT_MODE_AOA,
};
enum sc_mouse_input_mode {
SC_MOUSE_INPUT_MODE_INJECT,
SC_MOUSE_INPUT_MODE_HID,
SC_MOUSE_INPUT_MODE_AUTO,
SC_MOUSE_INPUT_MODE_DISABLED,
SC_MOUSE_INPUT_MODE_SDK,
SC_MOUSE_INPUT_MODE_AOA,
};
enum sc_key_inject_mode {

View File

@@ -419,12 +419,21 @@ scrcpy(struct scrcpy_options *options) {
sdl_set_hints(options->render_driver);
}
// Initialize the video subsystem even if --no-video or --no-video-playback
// is passed so that clipboard synchronization still works.
// <https://github.com/Genymobile/scrcpy/issues/4418>
if (SDL_Init(SDL_INIT_VIDEO)) {
LOGE("Could not initialize SDL video: %s", SDL_GetError());
goto end;
if (options->video_playback ||
(options->control && options->clipboard_autosync)) {
// Initialize the video subsystem even if --no-video or
// --no-video-playback is passed so that clipboard synchronization
// still works.
// <https://github.com/Genymobile/scrcpy/issues/4418>
if (SDL_Init(SDL_INIT_VIDEO)) {
// If it fails, it is an error only if video playback is enabled
if (options->video_playback) {
LOGE("Could not initialize SDL video: %s", SDL_GetError());
goto end;
} else {
LOGW("Could not initialize SDL video: %s", SDL_GetError());
}
}
}
if (options->audio_playback) {
@@ -534,11 +543,11 @@ scrcpy(struct scrcpy_options *options) {
if (options->control) {
#ifdef HAVE_USB
bool use_hid_keyboard =
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID;
bool use_hid_mouse =
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID;
if (use_hid_keyboard || use_hid_mouse) {
bool use_aoa_keyboard =
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
bool use_aoa_mouse =
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
if (use_aoa_keyboard || use_aoa_mouse) {
bool ok = sc_acksync_init(&s->acksync);
if (!ok) {
goto end;
@@ -581,7 +590,7 @@ scrcpy(struct scrcpy_options *options) {
goto aoa_hid_end;
}
if (use_hid_keyboard) {
if (use_aoa_keyboard) {
if (sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) {
hid_keyboard_initialized = true;
kp = &s->keyboard_hid.key_processor;
@@ -590,7 +599,7 @@ scrcpy(struct scrcpy_options *options) {
}
}
if (use_hid_mouse) {
if (use_aoa_mouse) {
if (sc_hid_mouse_init(&s->mouse_hid, &s->aoa)) {
hid_mouse_initialized = true;
mp = &s->mouse_hid.mouse_processor;
@@ -625,25 +634,23 @@ aoa_hid_end:
}
}
if (use_hid_keyboard && !hid_keyboard_initialized) {
LOGE("Fallback to default keyboard injection method "
"(-K/--hid-keyboard ignored)");
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
if (use_aoa_keyboard && !hid_keyboard_initialized) {
LOGE("Fallback to --keyboard=sdk (--keyboard=aoa ignored)");
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_SDK;
}
if (use_hid_mouse && !hid_mouse_initialized) {
LOGE("Fallback to default mouse injection method "
"(-M/--hid-mouse ignored)");
options->mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT;
if (use_aoa_mouse && !hid_mouse_initialized) {
LOGE("Fallback to --keyboard=sdk (--keyboard=aoa ignored)");
options->mouse_input_mode = SC_MOUSE_INPUT_MODE_SDK;
}
}
#else
assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_HID);
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_HID);
assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_AOA);
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_AOA);
#endif
// keyboard_input_mode may have been reset if HID mode failed
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_INJECT) {
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_SDK) {
sc_keyboard_inject_init(&s->keyboard_inject, &s->controller,
options->key_inject_mode,
options->forward_key_repeat);
@@ -651,7 +658,7 @@ aoa_hid_end:
}
// mouse_input_mode may have been reset if HID mode failed
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_INJECT) {
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
sc_mouse_inject_init(&s->mouse_inject, &s->controller);
mp = &s->mouse_inject.mouse_processor;
}

View File

@@ -62,7 +62,7 @@ scrcpy_otg(struct scrcpy_options *options) {
// Minimal SDL initialization
if (SDL_Init(SDL_INIT_EVENTS)) {
LOGE("Could not initialize SDL: %s", SDL_GetError());
return false;
return SCRCPY_EXIT_FAILURE;
}
atexit(SDL_Quit);
@@ -117,10 +117,15 @@ scrcpy_otg(struct scrcpy_options *options) {
}
aoa_initialized = true;
assert(options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA
|| options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_DISABLED);
assert(options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA
|| options->mouse_input_mode == SC_MOUSE_INPUT_MODE_DISABLED);
bool enable_keyboard =
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID;
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
bool enable_mouse =
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID;
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
// If neither --hid-keyboard or --hid-mouse is passed, enable both
if (!enable_keyboard && !enable_mouse) {

View File

@@ -233,10 +233,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server
- [`scrcpy-server-v2.2`][direct-scrcpy-server]
<sub>SHA-256: `c85c4aa84305efb69115cd497a120ebdd10258993b4cf123a8245b3d99d49874`</sub>
- [`scrcpy-server-v2.3.1`][direct-scrcpy-server]
<sub>SHA-256: `f6814822fc308a7a532f253485c9038183c6296a6c5df470a9e383b4f8e7605b`</sub>
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-server-v2.2
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-server-v2.3.1
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:

View File

@@ -18,6 +18,17 @@ scrcpy --video-source=display --audio-source=mic # force display AND micropho
scrcpy --video-source=camera --audio-source=output # force camera AND device audio output
```
Audio can be disabled:
```bash
# audio not captured at all
scrcpy --video-source=camera --no-audio
scrcpy --video-source=camera --no-audio --record=file.mp4
# audio captured and recorded, but not played
scrcpy --video-source=camera --no-audio-playback --record=file.mp4
```
## List

View File

@@ -85,7 +85,7 @@ way as <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
To disable automatic clipboard synchronization, use
`--no-clipboard-autosync`.
## Pinch-to-zoom
## Pinch-to-zoom, rotate and tilt simulation
To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_.
@@ -93,8 +93,12 @@ More precisely, hold down <kbd>Ctrl</kbd> while pressing the left-click button.
Until the left-click button is released, all mouse movements scale and rotate
the content (if supported by the app) relative to the center of the screen.
To simulate a tilt gesture: <kbd>Shift</kbd>+_click-and-move-up-or-down_.
Technically, _scrcpy_ generates additional touch events from a "virtual finger"
at a location inverted through the center of the screen.
at a location inverted through the center of the screen. When pressing
<kbd>Ctrl</kbd> the x and y coordinates are inverted. Using <kbd>Shift</kbd>
only inverts x.
## Key repeat

View File

@@ -49,7 +49,8 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| Synchronize clipboards and paste⁵ | <kbd>MOD</kbd>+<kbd>v</kbd>
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
| Pinch-to-zoom/rotate | <kbd>Ctrl</kbd>+_click-and-move_
| Tilt (slide vertically with 2 fingers) | <kbd>Shift</kbd>+_click-and-move_
| Drag & drop APK file | Install APK from computer
| Drag & drop non-APK file | [Push file to device](control.md#push-file-to-device)

View File

@@ -21,6 +21,13 @@ This will create a new video device in `/dev/videoN`, where `N` is an integer
(more [options](https://github.com/umlaeute/v4l2loopback#options) are available
to create several devices or devices with specific IDs).
If you encounter problems detecting your device with Chrome/WebRTC, you can try
`exclusive_caps` mode:
```
sudo modprobe v4l2loopback exclusive_caps=1
```
To list the enabled devices:
```bash

View File

@@ -4,14 +4,14 @@
Download the [latest release]:
- [`scrcpy-win64-v2.2.zip`][direct-win64] (64-bit)
<sub>SHA-256: `9f9da88ac4c8319dcb9bf852f2d9bba942bac663413383419cddf64eaa5685bd`</sub>
- [`scrcpy-win32-v2.2.zip`][direct-win32] (32-bit)
<sub>SHA-256: `cb84269fc847b8b880e320879492a1ae6c017b42175f03e199530f7a53be9d74`</sub>
- [`scrcpy-win64-v2.3.1.zip`][direct-win64] (64-bit)
<sub>SHA-256: `f1f78ac98214078425804e524a1bed515b9d4b8a05b78d210a4ced2b910b262d`</sub>
- [`scrcpy-win32-v2.3.1.zip`][direct-win32] (32-bit)
<sub>SHA-256: `5dffc2d432e9b8b5b0e16f12e71428c37c70d9124cfbe7620df0b41b7efe91ff`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-win64-v2.2.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-win32-v2.2.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-win64-v2.3.1.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-win32-v2.3.1.zip
and extract it.

View File

@@ -2,8 +2,8 @@
set -e
BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.2/scrcpy-server-v2.2
PREBUILT_SERVER_SHA256=c85c4aa84305efb69115cd497a120ebdd10258993b4cf123a8245b3d99d49874
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-server-v2.3.1
PREBUILT_SERVER_SHA256=f6814822fc308a7a532f253485c9038183c6296a6c5df470a9e383b4f8e7605b
echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

@@ -1,5 +1,5 @@
project('scrcpy', 'c',
version: 'v2.2',
version: '2.3.1',
meson_version: '>= 0.48',
default_options: [
'c_std=c11',

View File

@@ -7,8 +7,8 @@ android {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 34
versionCode 200
versionName "v2.2"
versionCode 20301
versionName "2.3.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@@ -12,7 +12,7 @@
set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=v2.2
SCRCPY_VERSION_NAME=2.3.1
PLATFORM=${ANDROID_PLATFORM:-34}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0}

View File

@@ -153,13 +153,14 @@ public final class AudioCapture {
previousRecorderTimestamp = timestamp.nanoTime;
} else {
if (nextPts == 0) {
Ln.w("Could not get any audio timestamp");
Ln.w("Could not get initial audio timestamp");
nextPts = System.nanoTime() / 1000;
}
// compute from previous timestamp and packet size
pts = nextPts;
}
long durationUs = r * 1000000 / (CHANNELS * BYTES_PER_SAMPLE * SAMPLE_RATE);
long durationUs = r * 1000000L / (CHANNELS * BYTES_PER_SAMPLE * SAMPLE_RATE);
nextPts = pts + durationUs;
if (previousPts != 0 && pts < previousPts + ONE_SAMPLE_US) {

View File

@@ -187,5 +187,7 @@ public final class CleanUp {
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
}
}
System.exit(0);
}
}

View File

@@ -45,11 +45,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 Size deviceSize;
private ScreenInfo screenInfo;
private RotationListener rotationListener;
private FoldListener foldListener;
@@ -116,8 +116,8 @@ public final class Device {
return;
}
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), displayInfo.getSize(), options.getCrop(),
options.getMaxSize(), options.getLockVideoOrientation());
deviceSize = displayInfo.getSize();
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
// notify
if (foldListener != null) {
foldListener.onFoldChanged(displayId, folded);

View File

@@ -49,6 +49,7 @@ public final class Workarounds {
}
public static void apply(boolean audio, boolean camera) {
boolean mustFillConfigurationController = false;
boolean mustFillAppInfo = false;
boolean mustFillAppContext = false;
@@ -85,11 +86,23 @@ public final class Workarounds {
mustFillAppContext = true;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// On some Samsung devices, DisplayManagerGlobal.getDisplayInfoLocked() calls ActivityThread.currentActivityThread().getConfiguration(),
// which requires a non-null ConfigurationController.
// ConfigurationController was introduced in Android 12, so do not attempt to set it on lower versions.
// <https://github.com/Genymobile/scrcpy/issues/4467>
mustFillConfigurationController = true;
}
if (mustFillConfigurationController) {
// Must be call before fillAppContext() because it is necessary to get a valid system context
fillConfigurationController();
}
if (mustFillAppInfo) {
Workarounds.fillAppInfo();
fillAppInfo();
}
if (mustFillAppContext) {
Workarounds.fillAppContext();
fillAppContext();
}
}
@@ -149,6 +162,22 @@ public final class Workarounds {
}
}
private static void fillConfigurationController() {
try {
Class<?> configurationControllerClass = Class.forName("android.app.ConfigurationController");
Class<?> activityThreadInternalClass = Class.forName("android.app.ActivityThreadInternal");
Constructor<?> configurationControllerConstructor = configurationControllerClass.getDeclaredConstructor(activityThreadInternalClass);
configurationControllerConstructor.setAccessible(true);
Object configurationController = configurationControllerConstructor.newInstance(ACTIVITY_THREAD);
Field configurationControllerField = ACTIVITY_THREAD_CLASS.getDeclaredField("mConfigurationController");
configurationControllerField.setAccessible(true);
configurationControllerField.set(ACTIVITY_THREAD, configurationController);
} catch (Throwable throwable) {
Ln.d("Could not fill configuration: " + throwable.getMessage());
}
}
static Context getSystemContext() {
try {
Method getSystemContextMethod = ACTIVITY_THREAD_CLASS.getDeclaredMethod("getSystemContext");
@@ -256,16 +285,28 @@ public final class Workarounds {
Method getParcelMethod = attributionSourceState.getClass().getDeclaredMethod("getParcel");
Parcel attributionSourceParcel = (Parcel) getParcelMethod.invoke(attributionSourceState);
// private native int native_setup(Object audiorecordThis,
// Object /*AudioAttributes*/ attributes,
// int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
// int buffSizeInBytes, int[] sessionId, @NonNull Parcel attributionSource,
// long nativeRecordInJavaObj, int maxSharedAudioHistoryMs);
Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class, int.class,
int.class, int.class, int.class, int[].class, Parcel.class, long.class, int.class);
nativeSetupMethod.setAccessible(true);
initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference<AudioRecord>(audioRecord), attributes, sampleRateArray,
channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session, attributionSourceParcel, 0L, 0);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
// private native int native_setup(Object audiorecordThis,
// Object /*AudioAttributes*/ attributes,
// int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
// int buffSizeInBytes, int[] sessionId, @NonNull Parcel attributionSource,
// long nativeRecordInJavaObj, int maxSharedAudioHistoryMs);
Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class,
int.class, int.class, int.class, int.class, int[].class, Parcel.class, long.class, int.class);
nativeSetupMethod.setAccessible(true);
initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference<AudioRecord>(audioRecord), attributes,
sampleRateArray, channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session,
attributionSourceParcel, 0L, 0);
} else {
// Android 14 added a new int parameter "halInputFlags"
// <https://github.com/aosp-mirror/platform_frameworks_base/commit/f6135d75db79b1d48fad3a3b3080d37be20a2313>
Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class,
int.class, int.class, int.class, int.class, int[].class, Parcel.class, long.class, int.class, int.class);
nativeSetupMethod.setAccessible(true);
initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference<AudioRecord>(audioRecord), attributes,
sampleRateArray, channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session,
attributionSourceParcel, 0L, 0, 0);
}
}
}

View File

@@ -41,8 +41,21 @@ public final class ClipboardManager {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class, int.class);
getMethodVersion = 2;
} catch (NoSuchMethodException e3) {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class, String.class);
getMethodVersion = 3;
try {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class, String.class);
getMethodVersion = 3;
} catch (NoSuchMethodException e4) {
try {
getPrimaryClipMethod = manager.getClass()
.getMethod("getPrimaryClip", String.class, String.class, int.class, int.class, boolean.class);
getMethodVersion = 4;
} catch (NoSuchMethodException e5) {
getPrimaryClipMethod = manager.getClass()
.getMethod("getPrimaryClip", String.class, String.class, String.class, String.class, int.class, int.class,
boolean.class);
getMethodVersion = 5;
}
}
}
}
}
@@ -87,8 +100,13 @@ public final class ClipboardManager {
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID);
case 2:
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0);
default:
case 3:
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID, null);
case 4:
// The last boolean parameter is "userOperate"
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0, true);
default:
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, null, null, FakeContext.ROOT_UID, 0, true);
}
}