Compare commits
1 Commits
issue5162.
...
icon_decod
Author | SHA1 | Date | |
---|---|---|---|
77ebafd96c |
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@ -1,3 +0,0 @@
|
||||
github: [rom1v]
|
||||
liberapay: rom1v
|
||||
custom: ["https://paypal.me/rom2v"]
|
26
.github/ISSUE_TEMPLATE/bug_report.md
vendored
26
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -7,25 +7,17 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
_Please read the [prerequisites] to run scrcpy._
|
||||
- [ ] I have read the [FAQ](https://github.com/Genymobile/scrcpy/blob/master/FAQ.md).
|
||||
- [ ] I have searched in existing [issues](https://github.com/Genymobile/scrcpy/issues).
|
||||
|
||||
[prerequisites]: https://github.com/Genymobile/scrcpy#prerequisites
|
||||
|
||||
_Also read the [FAQ] and check if your [issue][issues] already exists._
|
||||
|
||||
[FAQ]: https://github.com/Genymobile/scrcpy/blob/master/FAQ.md
|
||||
[issues]: https://github.com/Genymobile/scrcpy/issues
|
||||
|
||||
## Environment
|
||||
|
||||
- **OS:** [e.g. Debian, Windows, macOS...]
|
||||
- **Scrcpy version:** [e.g. 2.5]
|
||||
- **Installation method:** [e.g. manual build, apt, snap, brew, Windows release...]
|
||||
- **Device model:**
|
||||
- **Android version:** [e.g. 14]
|
||||
|
||||
## Describe the bug
|
||||
**Environment**
|
||||
- OS: [e.g. Debian, Windows, macOS...]
|
||||
- scrcpy version: [e.g. 1.12.1]
|
||||
- installation method: [e.g. manual build, apt, snap, brew, Windows release...]
|
||||
- device model:
|
||||
- Android version: [e.g. 10]
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
On errors, please provide the output of the console (and `adb logcat` if relevant).
|
||||
|
8
.github/ISSUE_TEMPLATE/question.md
vendored
8
.github/ISSUE_TEMPLATE/question.md
vendored
@ -1,8 +0,0 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question about scrcpy
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
23
README.md
23
README.md
@ -2,7 +2,7 @@
|
||||
source for the project. Do not download releases from random websites, even if
|
||||
their name contains `scrcpy`.**
|
||||
|
||||
# scrcpy (v2.6)
|
||||
# scrcpy (v2.4)
|
||||
|
||||
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
||||
|
||||
@ -53,16 +53,10 @@ Make sure you [enabled USB debugging][enable-adb] on your device(s).
|
||||
|
||||
[enable-adb]: https://developer.android.com/studio/debug/dev-options#enable
|
||||
|
||||
On some devices (especially Xiaomi), you might get the following error:
|
||||
|
||||
```
|
||||
java.lang.SecurityException: Injecting input events requires the caller (or the source of the instrumentation, if any) to have the INJECT_EVENTS permission.
|
||||
```
|
||||
|
||||
In that case, you need to enable [an additional option][control] `USB debugging
|
||||
(Security Settings)` (this is an item different from `USB debugging`) to control
|
||||
it using a keyboard and mouse. Rebooting the device is necessary once this
|
||||
option is set.
|
||||
On some devices, you also need to enable [an additional option][control] `USB
|
||||
debugging (Security Settings)` (this is an item different from `USB debugging`)
|
||||
to control it using a keyboard and mouse. Rebooting the device is necessary once
|
||||
this option is set.
|
||||
|
||||
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
|
||||
|
||||
@ -154,14 +148,11 @@ documented in the following pages:
|
||||
|
||||
## Contact
|
||||
|
||||
You can open an [issue] for bug reports, feature requests or general questions.
|
||||
|
||||
For bug reports, please read the [FAQ](FAQ.md) first, you might find a solution
|
||||
to your problem immediately.
|
||||
If you encounter a bug, please read the [FAQ](FAQ.md) first, then open an [issue].
|
||||
|
||||
[issue]: https://github.com/Genymobile/scrcpy/issues
|
||||
|
||||
You can also use:
|
||||
For general questions or discussions, you can also use:
|
||||
|
||||
- Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy)
|
||||
- Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app)
|
||||
|
@ -6,7 +6,6 @@ _scrcpy() {
|
||||
--audio-buffer=
|
||||
--audio-codec=
|
||||
--audio-codec-options=
|
||||
--audio-dup
|
||||
--audio-encoder=
|
||||
--audio-source=
|
||||
--audio-output-buffer=
|
||||
@ -26,6 +25,7 @@ _scrcpy() {
|
||||
-e --select-tcpip
|
||||
-f --fullscreen
|
||||
--force-adb-forward
|
||||
--forward-all-clicks
|
||||
-h --help
|
||||
-K
|
||||
--keyboard=
|
||||
@ -41,7 +41,6 @@ _scrcpy() {
|
||||
-M
|
||||
--max-fps=
|
||||
--mouse=
|
||||
--mouse-bind=
|
||||
-n --no-control
|
||||
-N --no-playback
|
||||
--no-audio
|
||||
@ -51,7 +50,6 @@ _scrcpy() {
|
||||
--no-downsize-on-error
|
||||
--no-key-repeat
|
||||
--no-mipmaps
|
||||
--no-mouse-hover
|
||||
--no-power-on
|
||||
--no-video
|
||||
--no-video-playback
|
||||
@ -112,7 +110,7 @@ _scrcpy() {
|
||||
return
|
||||
;;
|
||||
--audio-source)
|
||||
COMPREPLY=($(compgen -W 'output mic playback' -- "$cur"))
|
||||
COMPREPLY=($(compgen -W 'output mic' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--camera-facing)
|
||||
|
@ -13,9 +13,8 @@ arguments=(
|
||||
'--audio-buffer=[Configure the audio buffering delay (in milliseconds)]'
|
||||
'--audio-codec=[Select the audio codec]:codec:(opus aac flac raw)'
|
||||
'--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]'
|
||||
'--audio-dup=[Duplicate audio]'
|
||||
'--audio-encoder=[Use a specific MediaCodec audio encoder]'
|
||||
'--audio-source=[Select the audio source]:source:(output mic playback)'
|
||||
'--audio-source=[Select the audio source]:source:(output mic)'
|
||||
'--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]'
|
||||
{-b,--video-bit-rate=}'[Encode the video at the given bit-rate]'
|
||||
'--camera-ar=[Select the camera size by its aspect ratio]'
|
||||
@ -33,9 +32,10 @@ arguments=(
|
||||
{-e,--select-tcpip}'[Use TCP/IP device]'
|
||||
{-f,--fullscreen}'[Start in fullscreen]'
|
||||
'--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]'
|
||||
'-K[Use UHID keyboard (same as --keyboard=uhid)]'
|
||||
'--keyboard=[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)'
|
||||
'--keyboard[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)'
|
||||
'--kill-adb-on-close[Kill adb when scrcpy terminates]'
|
||||
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
|
||||
'--list-camera-sizes[List the valid camera capture sizes]'
|
||||
@ -46,8 +46,7 @@ arguments=(
|
||||
{-m,--max-size=}'[Limit both the width and height of the video to value]'
|
||||
'-M[Use UHID mouse (same as --mouse=uhid)]'
|
||||
'--max-fps=[Limit the frame rate of screen capture]'
|
||||
'--mouse=[Set the mouse input mode]:mode:(disabled sdk uhid aoa)'
|
||||
'--mouse-bind=[Configure bindings of secondary clicks]'
|
||||
'--mouse[Set the mouse input mode]:mode:(disabled sdk uhid 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]'
|
||||
@ -57,7 +56,6 @@ arguments=(
|
||||
'--no-downsize-on-error[Disable lowering definition on MediaCodec error]'
|
||||
'--no-key-repeat[Do not forward repeated key events when a key is held down]'
|
||||
'--no-mipmaps[Disable the generation of mipmaps]'
|
||||
'--no-mouse-hover[Do not forward mouse hover events]'
|
||||
'--no-power-on[Do not power on the device on start]'
|
||||
'--no-video[Disable video forwarding]'
|
||||
'--no-video-playback[Disable video playback]'
|
||||
|
@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
. common
|
||||
|
||||
VERSION=35.0.0
|
||||
VERSION=34.0.5
|
||||
FILENAME=platform-tools_r$VERSION-windows.zip
|
||||
PROJECT_DIR=platform-tools-$VERSION
|
||||
SHA256SUM=7ab78a8f8b305ae4d0de647d99c43599744de61a0838d3a47bda0cdffefee87e
|
||||
SHA256SUM=3f8320152704377de150418a3c4c9d07d16d80a6c0d0d8f7289c22c499e33571
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
|
@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
. common
|
||||
|
||||
VERSION=7.0.1
|
||||
VERSION=6.1.1
|
||||
FILENAME=ffmpeg-$VERSION.tar.xz
|
||||
PROJECT_DIR=ffmpeg-$VERSION
|
||||
SHA256SUM=bce9eeb0f17ef8982390b1f37711a61b4290dc8c2a0c1a37b5857e85bfb0e4ff
|
||||
SHA256SUM=8684f4b00f94b85461884c3719382f1261f0d9eb3d59640a1f4ac0873616f968
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
@ -17,6 +17,7 @@ then
|
||||
else
|
||||
get_file "https://ffmpeg.org/releases/$FILENAME" "$FILENAME" "$SHA256SUM"
|
||||
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
|
||||
patch -d "$PROJECT_DIR" -p1 < "$PATCHES_DIR"/ffmpeg-6.1-fix-build.patch
|
||||
fi
|
||||
|
||||
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
|
||||
|
27
app/deps/patches/ffmpeg-6.1-fix-build.patch
Normal file
27
app/deps/patches/ffmpeg-6.1-fix-build.patch
Normal file
@ -0,0 +1,27 @@
|
||||
From 03c80197afb324da38c9b70254231e3fdcfa68fc Mon Sep 17 00:00:00 2001
|
||||
From: Romain Vimont <rom@rom1v.com>
|
||||
Date: Sun, 12 Nov 2023 17:58:50 +0100
|
||||
Subject: [PATCH] Fix FFmpeg 6.1 build
|
||||
|
||||
Build failed on tag n6.1 With --enable-decoder=av1 but without
|
||||
--enable-muxer=av1.
|
||||
---
|
||||
libavcodec/Makefile | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
|
||||
index 580a8d6b54..aff19b670c 100644
|
||||
--- a/libavcodec/Makefile
|
||||
+++ b/libavcodec/Makefile
|
||||
@@ -249,7 +249,7 @@ OBJS-$(CONFIG_ATRAC3PAL_DECODER) += atrac3plusdec.o atrac3plus.o \
|
||||
OBJS-$(CONFIG_ATRAC9_DECODER) += atrac9dec.o
|
||||
OBJS-$(CONFIG_AURA_DECODER) += cyuv.o
|
||||
OBJS-$(CONFIG_AURA2_DECODER) += aura.o
|
||||
-OBJS-$(CONFIG_AV1_DECODER) += av1dec.o
|
||||
+OBJS-$(CONFIG_AV1_DECODER) += av1dec.o av1_parse.o
|
||||
OBJS-$(CONFIG_AV1_CUVID_DECODER) += cuviddec.o
|
||||
OBJS-$(CONFIG_AV1_MEDIACODEC_DECODER) += mediacodecdec.o
|
||||
OBJS-$(CONFIG_AV1_MEDIACODEC_ENCODER) += mediacodecenc.o
|
||||
--
|
||||
2.42.0
|
||||
|
@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||
cd "$DEPS_DIR"
|
||||
. common
|
||||
|
||||
VERSION=2.30.5
|
||||
VERSION=2.28.5
|
||||
FILENAME=SDL-$VERSION.tar.gz
|
||||
PROJECT_DIR=SDL-release-$VERSION
|
||||
SHA256SUM=be3ca88f8c362704627a0bc5406edb2cd6cc6ba463596d81ebb7c2f18763d3bf
|
||||
SHA256SUM=9f0556e4a24ef5b267010038ad9e9948b62f236d5bcc4b22179f95ef62d84023
|
||||
|
||||
cd "$SOURCES_DIR"
|
||||
|
||||
|
@ -13,7 +13,7 @@ BEGIN
|
||||
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
|
||||
VALUE "OriginalFilename", "scrcpy.exe"
|
||||
VALUE "ProductName", "scrcpy"
|
||||
VALUE "ProductVersion", "2.6"
|
||||
VALUE "ProductVersion", "2.4"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
47
app/scrcpy.1
47
app/scrcpy.1
@ -49,12 +49,6 @@ The list of possible codec options is available in the Android documentation:
|
||||
|
||||
<https://d.android.com/reference/android/media/MediaFormat>
|
||||
|
||||
.TP
|
||||
.B \-\-audio\-dup
|
||||
Duplicate audio (capture and keep playing on the device).
|
||||
|
||||
This feature is only available with --audio-source=playback.
|
||||
|
||||
.TP
|
||||
.BI "\-\-audio\-encoder " name
|
||||
Use a specific MediaCodec audio encoder (depending on the codec provided by \fB\-\-audio\-codec\fR).
|
||||
@ -63,13 +57,7 @@ The available encoders can be listed by \fB\-\-list\-encoders\fR.
|
||||
|
||||
.TP
|
||||
.BI "\-\-audio\-source " source
|
||||
Select the audio source (output, mic or playback).
|
||||
|
||||
The "output" source forwards the whole audio output, and disables playback on the device.
|
||||
|
||||
The "playback" source captures the audio playback (Android apps can opt-out, so the whole output is not necessarily captured).
|
||||
|
||||
The "mic" source captures the microphone.
|
||||
Select the audio source (output or mic).
|
||||
|
||||
Default is output.
|
||||
|
||||
@ -175,6 +163,10 @@ Start in fullscreen.
|
||||
.B \-\-force\-adb\-forward
|
||||
Do not attempt to use "adb reverse" to connect to the device.
|
||||
|
||||
.TP
|
||||
.B \-\-forward\-all\-clicks
|
||||
By default, right-click triggers BACK (or POWER on) and middle-click triggers HOME. This option disables these shortcuts and forward the clicks to the device instead.
|
||||
|
||||
.TP
|
||||
.B \-h, \-\-help
|
||||
Print this help.
|
||||
@ -269,27 +261,6 @@ LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse bac
|
||||
|
||||
Also see \fB\-\-keyboard\fR.
|
||||
|
||||
.TP
|
||||
.BI "\-\-mouse\-bind " xxxx[:xxxx]
|
||||
Configure bindings of secondary clicks.
|
||||
|
||||
The argument must be one or two sequences (separated by ':') of exactly 4 characters, one for each secondary click (in order: right click, middle click, 4th click, 5th click).
|
||||
|
||||
The first sequence defines the primary bindings, used when a mouse button is pressed alone. The second sequence defines the secondary bindings, used when a mouse button is pressed while the Shift key is held.
|
||||
|
||||
If the second sequence of bindings is omitted, then it is the same as the first one.
|
||||
|
||||
Each character must be one of the following:
|
||||
|
||||
- '+': forward the click to the device
|
||||
- '-': ignore the click
|
||||
- 'b': trigger shortcut BACK (or turn screen on if off)
|
||||
- 'h': trigger shortcut HOME
|
||||
- 's': trigger shortcut APP_SWITCH
|
||||
- 'n': trigger shortcut "expand notification panel"
|
||||
|
||||
Default is 'bhsn:++++' for SDK mouse, and '++++:bhsn' for AOA and UHID.
|
||||
|
||||
|
||||
.TP
|
||||
.B \-n, \-\-no\-control
|
||||
@ -333,10 +304,6 @@ Do not forward repeated key events when a key is held down.
|
||||
.B \-\-no\-mipmaps
|
||||
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
|
||||
|
||||
.TP
|
||||
.B \-\-no\-mouse\-hover
|
||||
Do not forward mouse hover (mouse motion without any clicks) events.
|
||||
|
||||
.TP
|
||||
.B \-\-no\-power\-on
|
||||
Do not power on the device on start.
|
||||
@ -457,9 +424,9 @@ Turn the device screen off immediately.
|
||||
.BI "\-\-shortcut\-mod " key\fR[+...]][,...]
|
||||
Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper".
|
||||
|
||||
Several shortcut modifiers can be specified, separated by ','.
|
||||
A shortcut can consist in several keys, separated by '+'. Several shortcuts can be specified, separated by ','.
|
||||
|
||||
For example, to use either LCtrl or LSuper for scrcpy shortcuts, pass "lctrl,lsuper".
|
||||
For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctrl+lalt,lsuper".
|
||||
|
||||
Default is "lalt,lsuper" (left-Alt or left-Super).
|
||||
|
||||
|
332
app/src/cli.c
332
app/src/cli.c
@ -98,9 +98,6 @@ enum {
|
||||
OPT_HID_KEYBOARD_DEPRECATED,
|
||||
OPT_HID_MOUSE_DEPRECATED,
|
||||
OPT_NO_WINDOW,
|
||||
OPT_MOUSE_BIND,
|
||||
OPT_NO_MOUSE_HOVER,
|
||||
OPT_AUDIO_DUP,
|
||||
};
|
||||
|
||||
struct sc_option {
|
||||
@ -178,13 +175,6 @@ static const struct sc_option options[] = {
|
||||
"Android documentation: "
|
||||
"<https://d.android.com/reference/android/media/MediaFormat>",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_AUDIO_DUP,
|
||||
.longopt = "audio-dup",
|
||||
.text = "Duplicate audio (capture and keep playing on the device).\n"
|
||||
"This feature is only available with --audio-source=playback."
|
||||
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_AUDIO_ENCODER,
|
||||
.longopt = "audio-encoder",
|
||||
@ -197,13 +187,7 @@ static const struct sc_option options[] = {
|
||||
.longopt_id = OPT_AUDIO_SOURCE,
|
||||
.longopt = "audio-source",
|
||||
.argdesc = "source",
|
||||
.text = "Select the audio source (output, mic or playback).\n"
|
||||
"The \"output\" source forwards the whole audio output, and "
|
||||
"disables playback on the device.\n"
|
||||
"The \"playback\" source captures the audio playback (Android "
|
||||
"apps can opt-out, so the whole output is not necessarily "
|
||||
"captured).\n"
|
||||
"The \"mic\" source captures the microphone.\n"
|
||||
.text = "Select the audio source (output or mic).\n"
|
||||
"Default is output.",
|
||||
},
|
||||
{
|
||||
@ -368,9 +352,11 @@ static const struct sc_option options[] = {
|
||||
"device.",
|
||||
},
|
||||
{
|
||||
// deprecated
|
||||
.longopt_id = OPT_FORWARD_ALL_CLICKS,
|
||||
.longopt = "forward-all-clicks",
|
||||
.text = "By default, right-click triggers BACK (or POWER on) and "
|
||||
"middle-click triggers HOME. This option disables these "
|
||||
"shortcuts and forwards the clicks to the device instead.",
|
||||
},
|
||||
{
|
||||
.shortopt = 'h',
|
||||
@ -504,30 +490,6 @@ static const struct sc_option options[] = {
|
||||
"control of the mouse back to the computer.\n"
|
||||
"Also see --keyboard.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_MOUSE_BIND,
|
||||
.longopt = "mouse-bind",
|
||||
.argdesc = "xxxx[:xxxx]",
|
||||
.text = "Configure bindings of secondary clicks.\n"
|
||||
"The argument must be one or two sequences (separated by ':') "
|
||||
"of exactly 4 characters, one for each secondary click (in "
|
||||
"order: right click, middle click, 4th click, 5th click).\n"
|
||||
"The first sequence defines the primary bindings, used when a "
|
||||
"mouse button is pressed alone. The second sequence defines "
|
||||
"the secondary bindings, used when a mouse button is pressed "
|
||||
"while the Shift key is held.\n"
|
||||
"If the second sequence of bindings is omitted, then it is the "
|
||||
"same as the first one.\n"
|
||||
"Each character must be one of the following:\n"
|
||||
" '+': forward the click to the device\n"
|
||||
" '-': ignore the click\n"
|
||||
" 'b': trigger shortcut BACK (or turn screen on if off)\n"
|
||||
" 'h': trigger shortcut HOME\n"
|
||||
" 's': trigger shortcut APP_SWITCH\n"
|
||||
" 'n': trigger shortcut \"expand notification panel\"\n"
|
||||
"Default is 'bhsn:++++' for SDK mouse, and '++++:bhsn' for AOA "
|
||||
"and UHID.",
|
||||
},
|
||||
{
|
||||
.shortopt = 'n',
|
||||
.longopt = "no-control",
|
||||
@ -590,12 +552,6 @@ static const struct sc_option options[] = {
|
||||
"mipmaps are automatically generated to improve downscaling "
|
||||
"quality. This option disables the generation of mipmaps.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_NO_MOUSE_HOVER,
|
||||
.longopt = "no-mouse-hover",
|
||||
.text = "Do not forward mouse hover (mouse motion without any clicks) "
|
||||
"events.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_NO_POWER_ON,
|
||||
.longopt = "no-power-on",
|
||||
@ -760,10 +716,10 @@ static const struct sc_option options[] = {
|
||||
.text = "Specify the modifiers to use for scrcpy shortcuts.\n"
|
||||
"Possible keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\", "
|
||||
"\"lsuper\" and \"rsuper\".\n"
|
||||
"Several shortcut modifiers can be specified, separated by "
|
||||
"','.\n"
|
||||
"For example, to use either LCtrl or LSuper for scrcpy "
|
||||
"shortcuts, pass \"lctrl,lsuper\".\n"
|
||||
"A shortcut can consist in several keys, separated by '+'. "
|
||||
"Several shortcuts can be specified, separated by ','.\n"
|
||||
"For example, to use either LCtrl+LAlt or LSuper for scrcpy "
|
||||
"shortcuts, pass \"lctrl+lalt,lsuper\".\n"
|
||||
"Default is \"lalt,lsuper\" (left-Alt or left-Super).",
|
||||
},
|
||||
{
|
||||
@ -1731,62 +1687,82 @@ parse_log_level(const char *s, enum sc_log_level *log_level) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static enum sc_shortcut_mod
|
||||
// item is a list of mod keys separated by '+' (e.g. "lctrl+lalt")
|
||||
// returns a bitwise-or of SC_SHORTCUT_MOD_* constants (or 0 on error)
|
||||
static unsigned
|
||||
parse_shortcut_mods_item(const char *item, size_t len) {
|
||||
unsigned mod = 0;
|
||||
|
||||
for (;;) {
|
||||
char *plus = strchr(item, '+');
|
||||
// strchr() does not consider the "len" parameter, to it could find an
|
||||
// occurrence too far in the string (there is no strnchr())
|
||||
bool has_plus = plus && plus < item + len;
|
||||
|
||||
assert(!has_plus || plus > item);
|
||||
size_t key_len = has_plus ? (size_t) (plus - item) : len;
|
||||
|
||||
#define STREQ(literal, s, len) \
|
||||
((sizeof(literal)-1 == len) && !memcmp(literal, s, len))
|
||||
|
||||
if (STREQ("lctrl", item, len)) {
|
||||
return SC_SHORTCUT_MOD_LCTRL;
|
||||
}
|
||||
if (STREQ("rctrl", item, len)) {
|
||||
return SC_SHORTCUT_MOD_RCTRL;
|
||||
}
|
||||
if (STREQ("lalt", item, len)) {
|
||||
return SC_SHORTCUT_MOD_LALT;
|
||||
}
|
||||
if (STREQ("ralt", item, len)) {
|
||||
return SC_SHORTCUT_MOD_RALT;
|
||||
}
|
||||
if (STREQ("lsuper", item, len)) {
|
||||
return SC_SHORTCUT_MOD_LSUPER;
|
||||
}
|
||||
if (STREQ("rsuper", item, len)) {
|
||||
return SC_SHORTCUT_MOD_RSUPER;
|
||||
}
|
||||
if (STREQ("lctrl", item, key_len)) {
|
||||
mod |= SC_SHORTCUT_MOD_LCTRL;
|
||||
} else if (STREQ("rctrl", item, key_len)) {
|
||||
mod |= SC_SHORTCUT_MOD_RCTRL;
|
||||
} else if (STREQ("lalt", item, key_len)) {
|
||||
mod |= SC_SHORTCUT_MOD_LALT;
|
||||
} else if (STREQ("ralt", item, key_len)) {
|
||||
mod |= SC_SHORTCUT_MOD_RALT;
|
||||
} else if (STREQ("lsuper", item, key_len)) {
|
||||
mod |= SC_SHORTCUT_MOD_LSUPER;
|
||||
} else if (STREQ("rsuper", item, key_len)) {
|
||||
mod |= SC_SHORTCUT_MOD_RSUPER;
|
||||
} else {
|
||||
LOGE("Unknown modifier key: %.*s "
|
||||
"(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)",
|
||||
(int) key_len, item);
|
||||
return 0;
|
||||
}
|
||||
#undef STREQ
|
||||
|
||||
bool has_plus = strchr(item, '+');
|
||||
if (has_plus) {
|
||||
LOGE("Shortcut mod combination with '+' is not supported anymore: "
|
||||
"'%.*s' (see #4741)", (int) len, item);
|
||||
return 0;
|
||||
if (!has_plus) {
|
||||
break;
|
||||
}
|
||||
|
||||
item = plus + 1;
|
||||
assert(len >= key_len + 1);
|
||||
len -= key_len + 1;
|
||||
}
|
||||
|
||||
LOGE("Unknown modifier key: %.*s "
|
||||
"(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)",
|
||||
(int) len, item);
|
||||
|
||||
return 0;
|
||||
return mod;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_shortcut_mods(const char *s, uint8_t *shortcut_mods) {
|
||||
uint8_t mods = 0;
|
||||
parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
|
||||
unsigned count = 0;
|
||||
unsigned current = 0;
|
||||
|
||||
// A list of shortcut modifiers, for example "lctrl,rctrl,rsuper"
|
||||
// LCtrl+LAlt or RCtrl or LCtrl+RSuper: "lctrl+lalt,rctrl,lctrl+rsuper"
|
||||
|
||||
for (;;) {
|
||||
char *comma = strchr(s, ',');
|
||||
assert(!comma || comma > s);
|
||||
size_t limit = comma ? (size_t) (comma - s) : strlen(s);
|
||||
|
||||
enum sc_shortcut_mod mod = parse_shortcut_mods_item(s, limit);
|
||||
if (!mod) {
|
||||
if (comma && count == SC_MAX_SHORTCUT_MODS - 1) {
|
||||
assert(count < SC_MAX_SHORTCUT_MODS);
|
||||
LOGW("Too many shortcut modifiers alternatives");
|
||||
return false;
|
||||
}
|
||||
|
||||
mods |= mod;
|
||||
assert(!comma || comma > s);
|
||||
size_t limit = comma ? (size_t) (comma - s) : strlen(s);
|
||||
|
||||
unsigned mod = parse_shortcut_mods_item(s, limit);
|
||||
if (!mod) {
|
||||
LOGE("Invalid modifier keys: %.*s", (int) limit, s);
|
||||
return false;
|
||||
}
|
||||
|
||||
mods->data[current++] = mod;
|
||||
++count;
|
||||
|
||||
if (!comma) {
|
||||
break;
|
||||
@ -1795,7 +1771,7 @@ parse_shortcut_mods(const char *s, uint8_t *shortcut_mods) {
|
||||
s = comma + 1;
|
||||
}
|
||||
|
||||
*shortcut_mods = mods;
|
||||
mods->count = count;
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -1803,7 +1779,7 @@ parse_shortcut_mods(const char *s, uint8_t *shortcut_mods) {
|
||||
#ifdef SC_TEST
|
||||
// expose the function to unit-tests
|
||||
bool
|
||||
sc_parse_shortcut_mods(const char *s, uint8_t *mods) {
|
||||
sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
|
||||
return parse_shortcut_mods(s, mods);
|
||||
}
|
||||
#endif
|
||||
@ -1945,13 +1921,7 @@ parse_audio_source(const char *optarg, enum sc_audio_source *source) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!strcmp(optarg, "playback")) {
|
||||
*source = SC_AUDIO_SOURCE_PLAYBACK;
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGE("Unsupported audio source: %s (expected output, mic or playback)",
|
||||
optarg);
|
||||
LOGE("Unsupported audio source: %s (expected output or mic)", optarg);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2088,85 +2058,11 @@ parse_pause_on_exit(const char *s, enum sc_pause_on_exit *pause_on_exit) {
|
||||
}
|
||||
|
||||
LOGE("Unsupported pause on exit mode: %s "
|
||||
"(expected true, false or if-error)", s);
|
||||
"(expected true, false or if-error)", optarg);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_mouse_binding(char c, enum sc_mouse_binding *b) {
|
||||
switch (c) {
|
||||
case '+':
|
||||
*b = SC_MOUSE_BINDING_CLICK;
|
||||
return true;
|
||||
case '-':
|
||||
*b = SC_MOUSE_BINDING_DISABLED;
|
||||
return true;
|
||||
case 'b':
|
||||
*b = SC_MOUSE_BINDING_BACK;
|
||||
return true;
|
||||
case 'h':
|
||||
*b = SC_MOUSE_BINDING_HOME;
|
||||
return true;
|
||||
case 's':
|
||||
*b = SC_MOUSE_BINDING_APP_SWITCH;
|
||||
return true;
|
||||
case 'n':
|
||||
*b = SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL;
|
||||
return true;
|
||||
default:
|
||||
LOGE("Invalid mouse binding: '%c' "
|
||||
"(expected '+', '-', 'b', 'h', 's' or 'n')", c);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_mouse_binding_set(const char *s, struct sc_mouse_binding_set *mbs) {
|
||||
assert(strlen(s) >= 4);
|
||||
|
||||
if (!parse_mouse_binding(s[0], &mbs->right_click)) {
|
||||
return false;
|
||||
}
|
||||
if (!parse_mouse_binding(s[1], &mbs->middle_click)) {
|
||||
return false;
|
||||
}
|
||||
if (!parse_mouse_binding(s[2], &mbs->click4)) {
|
||||
return false;
|
||||
}
|
||||
if (!parse_mouse_binding(s[3], &mbs->click5)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_mouse_bindings(const char *s, struct sc_mouse_bindings *mb) {
|
||||
size_t len = strlen(s);
|
||||
// either "xxxx" or "xxxx:xxxx"
|
||||
if (len != 4 && (len != 9 || s[4] != ':')) {
|
||||
LOGE("Invalid mouse bindings: '%s' (expected 'xxxx' or 'xxxx:xxxx', "
|
||||
"with each 'x' being in {'+', '-', 'b', 'h', 's', 'n'})", s);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!parse_mouse_binding_set(s, &mb->pri)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (len == 9) {
|
||||
if (!parse_mouse_binding_set(s + 5, &mb->sec)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// use the same bindings for Shift+click
|
||||
mb->sec = mb->pri;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
const char *optstring, const struct option *longopts) {
|
||||
@ -2249,14 +2145,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case OPT_MOUSE_BIND:
|
||||
if (!parse_mouse_bindings(optarg, &opts->mouse_bindings)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case OPT_NO_MOUSE_HOVER:
|
||||
opts->mouse_hover = false;
|
||||
break;
|
||||
case OPT_HID_MOUSE_DEPRECATED:
|
||||
LOGE("--hid-mouse has been removed, use --mouse=aoa or "
|
||||
"--mouse=uhid instead.");
|
||||
@ -2454,22 +2342,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
break;
|
||||
case OPT_FORWARD_ALL_CLICKS:
|
||||
LOGW("--forward-all-clicks is deprecated, "
|
||||
"use --mouse-bind=++++ instead.");
|
||||
opts->mouse_bindings = (struct sc_mouse_bindings) {
|
||||
.pri = {
|
||||
.right_click = SC_MOUSE_BINDING_CLICK,
|
||||
.middle_click = SC_MOUSE_BINDING_CLICK,
|
||||
.click4 = SC_MOUSE_BINDING_CLICK,
|
||||
.click5 = SC_MOUSE_BINDING_CLICK,
|
||||
},
|
||||
.sec = {
|
||||
.right_click = SC_MOUSE_BINDING_CLICK,
|
||||
.middle_click = SC_MOUSE_BINDING_CLICK,
|
||||
.click4 = SC_MOUSE_BINDING_CLICK,
|
||||
.click5 = SC_MOUSE_BINDING_CLICK,
|
||||
},
|
||||
};
|
||||
opts->forward_all_clicks = true;
|
||||
break;
|
||||
case OPT_LEGACY_PASTE:
|
||||
opts->legacy_paste = true;
|
||||
@ -2623,9 +2496,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
case OPT_NO_WINDOW:
|
||||
opts->window = false;
|
||||
break;
|
||||
case OPT_AUDIO_DUP:
|
||||
opts->audio_dup = true;
|
||||
break;
|
||||
default:
|
||||
// getopt prints the error message on stderr
|
||||
return false;
|
||||
@ -2760,40 +2630,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
}
|
||||
|
||||
// If mouse bindings are not explictly set, configure default bindings
|
||||
if (opts->mouse_bindings.pri.right_click == SC_MOUSE_BINDING_AUTO) {
|
||||
assert(opts->mouse_bindings.pri.middle_click == SC_MOUSE_BINDING_AUTO);
|
||||
assert(opts->mouse_bindings.pri.click4 == SC_MOUSE_BINDING_AUTO);
|
||||
assert(opts->mouse_bindings.pri.click5 == SC_MOUSE_BINDING_AUTO);
|
||||
assert(opts->mouse_bindings.sec.right_click == SC_MOUSE_BINDING_AUTO);
|
||||
assert(opts->mouse_bindings.sec.middle_click == SC_MOUSE_BINDING_AUTO);
|
||||
assert(opts->mouse_bindings.sec.click4 == SC_MOUSE_BINDING_AUTO);
|
||||
assert(opts->mouse_bindings.sec.click5 == SC_MOUSE_BINDING_AUTO);
|
||||
|
||||
static struct sc_mouse_binding_set default_shortcuts = {
|
||||
.right_click = SC_MOUSE_BINDING_BACK,
|
||||
.middle_click = SC_MOUSE_BINDING_HOME,
|
||||
.click4 = SC_MOUSE_BINDING_APP_SWITCH,
|
||||
.click5 = SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL,
|
||||
};
|
||||
|
||||
static struct sc_mouse_binding_set forward = {
|
||||
.right_click = SC_MOUSE_BINDING_CLICK,
|
||||
.middle_click = SC_MOUSE_BINDING_CLICK,
|
||||
.click4 = SC_MOUSE_BINDING_CLICK,
|
||||
.click5 = SC_MOUSE_BINDING_CLICK,
|
||||
};
|
||||
|
||||
// By default, forward all clicks only for UHID and AOA
|
||||
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
|
||||
opts->mouse_bindings.pri = default_shortcuts;
|
||||
opts->mouse_bindings.sec = forward;
|
||||
} else {
|
||||
opts->mouse_bindings.pri = forward;
|
||||
opts->mouse_bindings.sec = default_shortcuts;
|
||||
}
|
||||
}
|
||||
|
||||
if (otg) {
|
||||
if (!opts->control) {
|
||||
LOGE("--no-control is not allowed in OTG mode");
|
||||
@ -2838,12 +2674,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
}
|
||||
}
|
||||
|
||||
if (opts->mouse_input_mode != SC_MOUSE_INPUT_MODE_SDK
|
||||
&& !opts->mouse_hover) {
|
||||
LOGE("--no-mouse-over is specific to --mouse=sdk");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) {
|
||||
LOGI("Tunnel host/port is set, "
|
||||
"--force-adb-forward automatically enabled.");
|
||||
@ -2895,31 +2725,13 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||
if (opts->audio && opts->audio_source == SC_AUDIO_SOURCE_AUTO) {
|
||||
// Select the audio source according to the video source
|
||||
if (opts->video_source == SC_VIDEO_SOURCE_DISPLAY) {
|
||||
if (opts->audio_dup) {
|
||||
LOGI("Audio duplication enabled: audio source switched to "
|
||||
"\"playback\"");
|
||||
opts->audio_source = SC_AUDIO_SOURCE_PLAYBACK;
|
||||
} else {
|
||||
opts->audio_source = SC_AUDIO_SOURCE_OUTPUT;
|
||||
}
|
||||
opts->audio_source = SC_AUDIO_SOURCE_OUTPUT;
|
||||
} else {
|
||||
opts->audio_source = SC_AUDIO_SOURCE_MIC;
|
||||
LOGI("Camera video source: microphone audio source selected");
|
||||
}
|
||||
}
|
||||
|
||||
if (opts->audio_dup) {
|
||||
if (!opts->audio) {
|
||||
LOGE("--audio-dup not supported if audio is disabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->audio_source != SC_AUDIO_SOURCE_PLAYBACK) {
|
||||
LOGE("--audio-dup is specific to --audio-source=playback");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (opts->record_format && !opts->record_filename) {
|
||||
LOGE("Record format specified without recording");
|
||||
return false;
|
||||
|
@ -28,7 +28,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]);
|
||||
|
||||
#ifdef SC_TEST
|
||||
bool
|
||||
sc_parse_shortcut_mods(const char *s, uint8_t *shortcut_mods);
|
||||
sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -64,11 +64,13 @@ static const char *const copy_key_labels[] = {
|
||||
static inline const char *
|
||||
get_well_known_pointer_id_name(uint64_t pointer_id) {
|
||||
switch (pointer_id) {
|
||||
case SC_POINTER_ID_MOUSE:
|
||||
case POINTER_ID_MOUSE:
|
||||
return "mouse";
|
||||
case SC_POINTER_ID_GENERIC_FINGER:
|
||||
case POINTER_ID_GENERIC_FINGER:
|
||||
return "finger";
|
||||
case SC_POINTER_ID_VIRTUAL_FINGER:
|
||||
case POINTER_ID_VIRTUAL_MOUSE:
|
||||
return "vmouse";
|
||||
case POINTER_ID_VIRTUAL_FINGER:
|
||||
return "vfinger";
|
||||
default:
|
||||
return NULL;
|
||||
|
@ -18,11 +18,12 @@
|
||||
// type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes
|
||||
#define SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (SC_CONTROL_MSG_MAX_SIZE - 14)
|
||||
|
||||
#define SC_POINTER_ID_MOUSE UINT64_C(-1)
|
||||
#define SC_POINTER_ID_GENERIC_FINGER UINT64_C(-2)
|
||||
#define POINTER_ID_MOUSE UINT64_C(-1)
|
||||
#define POINTER_ID_GENERIC_FINGER UINT64_C(-2)
|
||||
|
||||
// Used for injecting an additional virtual pointer for pinch-to-zoom
|
||||
#define SC_POINTER_ID_VIRTUAL_FINGER UINT64_C(-3)
|
||||
#define POINTER_ID_VIRTUAL_MOUSE UINT64_C(-3)
|
||||
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-4)
|
||||
|
||||
enum sc_control_msg_type {
|
||||
SC_CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
||||
|
@ -7,13 +7,12 @@
|
||||
#define SC_CONTROL_MSG_QUEUE_MAX 64
|
||||
|
||||
static void
|
||||
sc_controller_receiver_on_ended(struct sc_receiver *receiver, bool error,
|
||||
void *userdata) {
|
||||
sc_controller_receiver_on_error(struct sc_receiver *receiver, void *userdata) {
|
||||
(void) receiver;
|
||||
|
||||
struct sc_controller *controller = userdata;
|
||||
// Forward the event to the controller listener
|
||||
controller->cbs->on_ended(controller, error, controller->cbs_userdata);
|
||||
controller->cbs->on_error(controller, controller->cbs_userdata);
|
||||
}
|
||||
|
||||
bool
|
||||
@ -28,7 +27,7 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
||||
}
|
||||
|
||||
static const struct sc_receiver_callbacks receiver_cbs = {
|
||||
.on_ended = sc_controller_receiver_on_ended,
|
||||
.on_error = sc_controller_receiver_on_error,
|
||||
};
|
||||
|
||||
ok = sc_receiver_init(&controller->receiver, control_socket, &receiver_cbs,
|
||||
@ -56,7 +55,7 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
|
||||
controller->control_socket = control_socket;
|
||||
controller->stopped = false;
|
||||
|
||||
assert(cbs && cbs->on_ended);
|
||||
assert(cbs && cbs->on_error);
|
||||
controller->cbs = cbs;
|
||||
controller->cbs_userdata = cbs_userdata;
|
||||
|
||||
@ -111,30 +110,21 @@ sc_controller_push_msg(struct sc_controller *controller,
|
||||
|
||||
static bool
|
||||
process_msg(struct sc_controller *controller,
|
||||
const struct sc_control_msg *msg, bool *eos) {
|
||||
const struct sc_control_msg *msg) {
|
||||
static uint8_t serialized_msg[SC_CONTROL_MSG_MAX_SIZE];
|
||||
size_t length = sc_control_msg_serialize(msg, serialized_msg);
|
||||
if (!length) {
|
||||
*eos = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t w =
|
||||
net_send_all(controller->control_socket, serialized_msg, length);
|
||||
if ((size_t) w != length) {
|
||||
*eos = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return (size_t) w == length;
|
||||
}
|
||||
|
||||
static int
|
||||
run_controller(void *data) {
|
||||
struct sc_controller *controller = data;
|
||||
|
||||
bool error = false;
|
||||
|
||||
for (;;) {
|
||||
sc_mutex_lock(&controller->mutex);
|
||||
while (!controller->stopped
|
||||
@ -144,7 +134,6 @@ run_controller(void *data) {
|
||||
if (controller->stopped) {
|
||||
// stop immediately, do not process further msgs
|
||||
sc_mutex_unlock(&controller->mutex);
|
||||
LOGD("Controller stopped");
|
||||
break;
|
||||
}
|
||||
|
||||
@ -152,21 +141,20 @@ run_controller(void *data) {
|
||||
struct sc_control_msg msg = sc_vecdeque_pop(&controller->queue);
|
||||
sc_mutex_unlock(&controller->mutex);
|
||||
|
||||
bool eos;
|
||||
bool ok = process_msg(controller, &msg, &eos);
|
||||
bool ok = process_msg(controller, &msg);
|
||||
sc_control_msg_destroy(&msg);
|
||||
if (!ok) {
|
||||
if (eos) {
|
||||
LOGD("Controller stopped (socket closed)");
|
||||
} // else error already logged
|
||||
error = !eos;
|
||||
break;
|
||||
LOGD("Could not write msg to socket");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
controller->cbs->on_ended(controller, error, controller->cbs_userdata);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
controller->cbs->on_error(controller, controller->cbs_userdata);
|
||||
|
||||
return 1; // ignored
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -28,8 +28,7 @@ struct sc_controller {
|
||||
};
|
||||
|
||||
struct sc_controller_callbacks {
|
||||
void (*on_ended)(struct sc_controller *controller, bool error,
|
||||
void *userdata);
|
||||
void (*on_error)(struct sc_controller *controller, void *userdata);
|
||||
};
|
||||
|
||||
bool
|
||||
|
@ -278,6 +278,7 @@ run_demuxer(void *data) {
|
||||
finally_close_sinks:
|
||||
sc_packet_source_sinks_close(&demuxer->packet_source);
|
||||
finally_free_context:
|
||||
// This also calls avcodec_close() internally
|
||||
avcodec_free_context(&codec_ctx);
|
||||
end:
|
||||
demuxer->cbs->on_ended(demuxer, status, demuxer->cbs_userdata);
|
||||
|
@ -43,10 +43,6 @@ sc_display_init(struct sc_display *display, SDL_Window *window,
|
||||
|
||||
display->mipmaps = false;
|
||||
|
||||
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
|
||||
display->gl_context = NULL;
|
||||
#endif
|
||||
|
||||
// starts with "opengl"
|
||||
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||
if (use_opengl) {
|
||||
|
@ -78,16 +78,7 @@ decode_image(const char *path) {
|
||||
goto close_input;
|
||||
}
|
||||
|
||||
|
||||
// In ffmpeg/doc/APIchanges:
|
||||
// 2021-04-27 - 46dac8cf3d - lavf 59.0.100 - avformat.h
|
||||
// av_find_best_stream now uses a const AVCodec ** parameter
|
||||
// for the returned decoder.
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(59, 0, 100)
|
||||
const AVCodec *codec;
|
||||
#else
|
||||
AVCodec *codec;
|
||||
#endif
|
||||
|
||||
int stream =
|
||||
av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
|
||||
@ -117,21 +108,21 @@ decode_image(const char *path) {
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
if (!frame) {
|
||||
LOG_OOM();
|
||||
goto free_codec_ctx;
|
||||
goto close_codec;
|
||||
}
|
||||
|
||||
AVPacket *packet = av_packet_alloc();
|
||||
if (!packet) {
|
||||
LOG_OOM();
|
||||
av_frame_free(&frame);
|
||||
goto free_codec_ctx;
|
||||
goto close_codec;
|
||||
}
|
||||
|
||||
if (av_read_frame(ctx, packet) < 0) {
|
||||
LOGE("Could not read frame");
|
||||
av_packet_free(&packet);
|
||||
av_frame_free(&frame);
|
||||
goto free_codec_ctx;
|
||||
goto close_codec;
|
||||
}
|
||||
|
||||
int ret;
|
||||
@ -139,20 +130,22 @@ decode_image(const char *path) {
|
||||
LOGE("Could not send icon packet: %d", ret);
|
||||
av_packet_free(&packet);
|
||||
av_frame_free(&frame);
|
||||
goto free_codec_ctx;
|
||||
goto close_codec;
|
||||
}
|
||||
|
||||
if ((ret = avcodec_receive_frame(codec_ctx, frame)) != 0) {
|
||||
LOGE("Could not receive icon frame: %d", ret);
|
||||
av_packet_free(&packet);
|
||||
av_frame_free(&frame);
|
||||
goto free_codec_ctx;
|
||||
goto close_codec;
|
||||
}
|
||||
|
||||
av_packet_free(&packet);
|
||||
|
||||
result = frame;
|
||||
|
||||
close_codec:
|
||||
avcodec_close(codec_ctx);
|
||||
free_codec_ctx:
|
||||
avcodec_free_context(&codec_ctx);
|
||||
close_input:
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include <SDL2/SDL_events.h>
|
||||
|
||||
#include "coords.h"
|
||||
#include "options.h"
|
||||
|
||||
/* The representation of input events in scrcpy is very close to the SDL API,
|
||||
* for simplicity.
|
||||
@ -437,11 +436,19 @@ sc_mouse_button_from_sdl(uint8_t button) {
|
||||
}
|
||||
|
||||
static inline uint8_t
|
||||
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state) {
|
||||
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state,
|
||||
bool forward_all_clicks) {
|
||||
assert(buttons_state < 0x100); // fits in uint8_t
|
||||
|
||||
// SC_MOUSE_BUTTON_* constants are initialized from SDL_BUTTON(index)
|
||||
return buttons_state;
|
||||
uint8_t mask = SC_MOUSE_BUTTON_LEFT;
|
||||
if (forward_all_clicks) {
|
||||
mask |= SC_MOUSE_BUTTON_RIGHT
|
||||
| SC_MOUSE_BUTTON_MIDDLE
|
||||
| SC_MOUSE_BUTTON_X1
|
||||
| SC_MOUSE_BUTTON_X2;
|
||||
}
|
||||
|
||||
return buttons_state & mask;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -10,7 +10,7 @@
|
||||
#define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI)
|
||||
|
||||
static inline uint16_t
|
||||
to_sdl_mod(uint8_t shortcut_mod) {
|
||||
to_sdl_mod(unsigned shortcut_mod) {
|
||||
uint16_t sdl_mod = 0;
|
||||
if (shortcut_mod & SC_SHORTCUT_MOD_LCTRL) {
|
||||
sdl_mod |= KMOD_LCTRL;
|
||||
@ -38,18 +38,15 @@ is_shortcut_mod(struct sc_input_manager *im, uint16_t sdl_mod) {
|
||||
// keep only the relevant modifier keys
|
||||
sdl_mod &= SC_SDL_SHORTCUT_MODS_MASK;
|
||||
|
||||
// at least one shortcut mod pressed?
|
||||
return sdl_mod & im->sdl_shortcut_mods;
|
||||
}
|
||||
assert(im->sdl_shortcut_mods.count);
|
||||
assert(im->sdl_shortcut_mods.count < SC_MAX_SHORTCUT_MODS);
|
||||
for (unsigned i = 0; i < im->sdl_shortcut_mods.count; ++i) {
|
||||
if (im->sdl_shortcut_mods.data[i] == sdl_mod) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
is_shortcut_key(struct sc_input_manager *im, SDL_Keycode keycode) {
|
||||
return (im->sdl_shortcut_mods & KMOD_LCTRL && keycode == SDLK_LCTRL)
|
||||
|| (im->sdl_shortcut_mods & KMOD_RCTRL && keycode == SDLK_RCTRL)
|
||||
|| (im->sdl_shortcut_mods & KMOD_LALT && keycode == SDLK_LALT)
|
||||
|| (im->sdl_shortcut_mods & KMOD_RALT && keycode == SDLK_RALT)
|
||||
|| (im->sdl_shortcut_mods & KMOD_LGUI && keycode == SDLK_LGUI)
|
||||
|| (im->sdl_shortcut_mods & KMOD_RGUI && keycode == SDLK_RGUI);
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
@ -67,18 +64,24 @@ sc_input_manager_init(struct sc_input_manager *im,
|
||||
im->kp = params->kp;
|
||||
im->mp = params->mp;
|
||||
|
||||
im->mouse_bindings = params->mouse_bindings;
|
||||
im->forward_all_clicks = params->forward_all_clicks;
|
||||
im->legacy_paste = params->legacy_paste;
|
||||
im->clipboard_autosync = params->clipboard_autosync;
|
||||
|
||||
im->sdl_shortcut_mods = to_sdl_mod(params->shortcut_mods);
|
||||
const struct sc_shortcut_mods *shortcut_mods = params->shortcut_mods;
|
||||
assert(shortcut_mods->count);
|
||||
assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS);
|
||||
for (unsigned i = 0; i < shortcut_mods->count; ++i) {
|
||||
uint16_t sdl_mod = to_sdl_mod(shortcut_mods->data[i]);
|
||||
assert(sdl_mod);
|
||||
im->sdl_shortcut_mods.data[i] = sdl_mod;
|
||||
}
|
||||
im->sdl_shortcut_mods.count = shortcut_mods->count;
|
||||
|
||||
im->vfinger_down = false;
|
||||
im->vfinger_invert_x = false;
|
||||
im->vfinger_invert_y = false;
|
||||
|
||||
im->mouse_buttons_state = 0;
|
||||
|
||||
im->last_keycode = SDLK_UNKNOWN;
|
||||
im->last_mod = 0;
|
||||
im->key_repeat = 0;
|
||||
@ -367,7 +370,9 @@ simulate_virtual_finger(struct sc_input_manager *im,
|
||||
msg.inject_touch_event.action = action;
|
||||
msg.inject_touch_event.position.screen_size = im->screen->frame_size;
|
||||
msg.inject_touch_event.position.point = point;
|
||||
msg.inject_touch_event.pointer_id = SC_POINTER_ID_VIRTUAL_FINGER;
|
||||
msg.inject_touch_event.pointer_id =
|
||||
im->forward_all_clicks ? POINTER_ID_VIRTUAL_MOUSE
|
||||
: POINTER_ID_VIRTUAL_FINGER;
|
||||
msg.inject_touch_event.pressure = up ? 0.0f : 1.0f;
|
||||
msg.inject_touch_event.action_button = 0;
|
||||
msg.inject_touch_event.buttons = 0;
|
||||
@ -407,12 +412,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
bool shift = event->keysym.mod & KMOD_SHIFT;
|
||||
bool repeat = event->repeat;
|
||||
|
||||
// Either the modifier includes a shortcut modifier, or the key
|
||||
// press/release is a modifier key.
|
||||
// The second condition is necessary to ignore the release of the modifier
|
||||
// key (because in this case mod is 0).
|
||||
bool is_shortcut = is_shortcut_mod(im, mod)
|
||||
|| is_shortcut_key(im, keycode);
|
||||
bool smod = is_shortcut_mod(im, mod);
|
||||
|
||||
if (down && !repeat) {
|
||||
if (keycode == im->last_keycode && mod == im->last_mod) {
|
||||
@ -424,7 +424,8 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
}
|
||||
|
||||
if (is_shortcut) {
|
||||
// The shortcut modifier is pressed
|
||||
if (smod) {
|
||||
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
||||
switch (keycode) {
|
||||
case SDLK_h:
|
||||
@ -652,11 +653,13 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
|
||||
|
||||
struct sc_mouse_motion_event evt = {
|
||||
.position = sc_input_manager_get_position(im, event->x, event->y),
|
||||
.pointer_id = im->vfinger_down ? SC_POINTER_ID_GENERIC_FINGER
|
||||
: SC_POINTER_ID_MOUSE,
|
||||
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
|
||||
: POINTER_ID_GENERIC_FINGER,
|
||||
.xrel = event->xrel,
|
||||
.yrel = event->yrel,
|
||||
.buttons_state = im->mouse_buttons_state,
|
||||
.buttons_state =
|
||||
sc_mouse_buttons_state_from_sdl(event->state,
|
||||
im->forward_all_clicks),
|
||||
};
|
||||
|
||||
assert(im->mp->ops->process_mouse_motion);
|
||||
@ -707,25 +710,6 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
|
||||
im->mp->ops->process_touch(im->mp, &evt);
|
||||
}
|
||||
|
||||
static enum sc_mouse_binding
|
||||
sc_input_manager_get_binding(const struct sc_mouse_binding_set *bindings,
|
||||
uint8_t sdl_button) {
|
||||
switch (sdl_button) {
|
||||
case SDL_BUTTON_LEFT:
|
||||
return SC_MOUSE_BINDING_CLICK;
|
||||
case SDL_BUTTON_RIGHT:
|
||||
return bindings->right_click;
|
||||
case SDL_BUTTON_MIDDLE:
|
||||
return bindings->middle_click;
|
||||
case SDL_BUTTON_X1:
|
||||
return bindings->click4;
|
||||
case SDL_BUTTON_X2:
|
||||
return bindings->click5;
|
||||
default:
|
||||
return SC_MOUSE_BINDING_DISABLED;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
const SDL_MouseButtonEvent *event) {
|
||||
@ -737,100 +721,66 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
bool control = im->controller;
|
||||
bool paused = im->screen->paused;
|
||||
bool down = event->type == SDL_MOUSEBUTTONDOWN;
|
||||
if (!im->forward_all_clicks) {
|
||||
if (control && !paused) {
|
||||
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
||||
|
||||
enum sc_mouse_button button = sc_mouse_button_from_sdl(event->button);
|
||||
if (!down) {
|
||||
// Mark the button as released
|
||||
im->mouse_buttons_state &= ~button;
|
||||
}
|
||||
|
||||
SDL_Keymod keymod = SDL_GetModState();
|
||||
bool ctrl_pressed = keymod & KMOD_CTRL;
|
||||
bool shift_pressed = keymod & KMOD_SHIFT;
|
||||
|
||||
if (control && !paused) {
|
||||
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
|
||||
|
||||
struct sc_mouse_binding_set *bindings = !shift_pressed
|
||||
? &im->mouse_bindings.pri
|
||||
: &im->mouse_bindings.sec;
|
||||
enum sc_mouse_binding binding =
|
||||
sc_input_manager_get_binding(bindings, event->button);
|
||||
assert(binding != SC_MOUSE_BINDING_AUTO);
|
||||
switch (binding) {
|
||||
case SC_MOUSE_BINDING_DISABLED:
|
||||
// ignore click
|
||||
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(im);
|
||||
} else {
|
||||
expand_settings_panel(im);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (im->kp && event->button == SDL_BUTTON_RIGHT) {
|
||||
press_back_or_turn_screen_on(im, action);
|
||||
return;
|
||||
}
|
||||
if (im->kp && event->button == SDL_BUTTON_MIDDLE) {
|
||||
action_home(im, action);
|
||||
return;
|
||||
case SC_MOUSE_BINDING_BACK:
|
||||
if (im->kp) {
|
||||
press_back_or_turn_screen_on(im, action);
|
||||
}
|
||||
return;
|
||||
case SC_MOUSE_BINDING_HOME:
|
||||
if (im->kp) {
|
||||
action_home(im, action);
|
||||
}
|
||||
return;
|
||||
case SC_MOUSE_BINDING_APP_SWITCH:
|
||||
if (im->kp) {
|
||||
action_app_switch(im, action);
|
||||
}
|
||||
return;
|
||||
case SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL:
|
||||
if (down) {
|
||||
if (event->clicks < 2) {
|
||||
expand_notification_panel(im);
|
||||
} else {
|
||||
expand_settings_panel(im);
|
||||
}
|
||||
}
|
||||
return;
|
||||
default:
|
||||
assert(binding == SC_MOUSE_BINDING_CLICK);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// double-click on black borders resizes to fit the device screen
|
||||
bool video = im->screen->video;
|
||||
bool mouse_relative_mode = im->mp && im->mp->relative_mode;
|
||||
if (video && !mouse_relative_mode && event->button == SDL_BUTTON_LEFT
|
||||
&& event->clicks == 2) {
|
||||
int32_t x = event->x;
|
||||
int32_t y = event->y;
|
||||
sc_screen_hidpi_scale_coords(im->screen, &x, &y);
|
||||
SDL_Rect *r = &im->screen->rect;
|
||||
bool outside = x < r->x || x >= r->x + r->w
|
||||
|| y < r->y || y >= r->y + r->h;
|
||||
if (outside) {
|
||||
if (down) {
|
||||
sc_screen_resize_to_fit(im->screen);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// double-click on black borders resize to fit the device screen
|
||||
bool video = im->screen->video;
|
||||
if (video && event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
|
||||
int32_t x = event->x;
|
||||
int32_t y = event->y;
|
||||
sc_screen_hidpi_scale_coords(im->screen, &x, &y);
|
||||
SDL_Rect *r = &im->screen->rect;
|
||||
bool outside = x < r->x || x >= r->x + r->w
|
||||
|| y < r->y || y >= r->y + r->h;
|
||||
if (outside) {
|
||||
if (down) {
|
||||
sc_screen_resize_to_fit(im->screen);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// otherwise, send the click event to the device
|
||||
}
|
||||
|
||||
if (!im->mp || paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (down) {
|
||||
// Mark the button as pressed
|
||||
im->mouse_buttons_state |= button;
|
||||
}
|
||||
|
||||
bool change_vfinger = event->button == SDL_BUTTON_LEFT &&
|
||||
((down && !im->vfinger_down && (ctrl_pressed ^ shift_pressed)) ||
|
||||
(!down && im->vfinger_down));
|
||||
bool use_finger = im->vfinger_down || change_vfinger;
|
||||
uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL);
|
||||
|
||||
struct sc_mouse_click_event evt = {
|
||||
.position = sc_input_manager_get_position(im, event->x, event->y),
|
||||
.action = sc_action_from_sdl_mousebutton_type(event->type),
|
||||
.button = sc_mouse_button_from_sdl(event->button),
|
||||
.pointer_id = use_finger ? SC_POINTER_ID_GENERIC_FINGER
|
||||
: SC_POINTER_ID_MOUSE,
|
||||
.buttons_state = im->mouse_buttons_state,
|
||||
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
|
||||
: POINTER_ID_GENERIC_FINGER,
|
||||
.buttons_state =
|
||||
sc_mouse_buttons_state_from_sdl(sdl_buttons_state,
|
||||
im->forward_all_clicks),
|
||||
};
|
||||
|
||||
assert(im->mp->ops->process_mouse_click);
|
||||
@ -856,7 +806,14 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
|
||||
// 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.
|
||||
if (change_vfinger) {
|
||||
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 && !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);
|
||||
@ -889,7 +846,6 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
|
||||
int mouse_x;
|
||||
int mouse_y;
|
||||
uint32_t buttons = SDL_GetMouseState(&mouse_x, &mouse_y);
|
||||
(void) buttons; // Actual buttons are tracked manually to ignore shortcuts
|
||||
|
||||
struct sc_mouse_scroll_event evt = {
|
||||
.position = sc_input_manager_get_position(im, mouse_x, mouse_y),
|
||||
@ -900,7 +856,8 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
|
||||
.hscroll = CLAMP(event->x, -1, 1),
|
||||
.vscroll = CLAMP(event->y, -1, 1),
|
||||
#endif
|
||||
.buttons_state = im->mouse_buttons_state,
|
||||
.buttons_state =
|
||||
sc_mouse_buttons_state_from_sdl(buttons, im->forward_all_clicks),
|
||||
};
|
||||
|
||||
im->mp->ops->process_mouse_scroll(im->mp, &evt);
|
||||
|
@ -22,18 +22,19 @@ struct sc_input_manager {
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
bool forward_all_clicks;
|
||||
bool legacy_paste;
|
||||
bool clipboard_autosync;
|
||||
|
||||
uint16_t sdl_shortcut_mods;
|
||||
struct {
|
||||
unsigned data[SC_MAX_SHORTCUT_MODS];
|
||||
unsigned count;
|
||||
} sdl_shortcut_mods;
|
||||
|
||||
bool vfinger_down;
|
||||
bool vfinger_invert_x;
|
||||
bool vfinger_invert_y;
|
||||
|
||||
uint8_t mouse_buttons_state; // OR of enum sc_mouse_button values
|
||||
|
||||
// Tracks the number of identical consecutive shortcut key down events.
|
||||
// Not to be confused with event->repeat, which counts the number of
|
||||
// system-generated repeated key presses.
|
||||
@ -51,10 +52,10 @@ struct sc_input_manager_params {
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
bool forward_all_clicks;
|
||||
bool legacy_paste;
|
||||
bool clipboard_autosync;
|
||||
uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
|
||||
const struct sc_shortcut_mods *shortcut_mods;
|
||||
};
|
||||
|
||||
void
|
||||
|
@ -58,18 +58,17 @@ convert_touch_action(enum sc_touch_action action) {
|
||||
static void
|
||||
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
||||
const struct sc_mouse_motion_event *event) {
|
||||
struct sc_mouse_sdk *m = DOWNCAST(mp);
|
||||
|
||||
if (!m->mouse_hover && !event->buttons_state) {
|
||||
if (!event->buttons_state) {
|
||||
// Do not send motion events when no click is pressed
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_mouse_sdk *m = DOWNCAST(mp);
|
||||
|
||||
struct sc_control_msg msg = {
|
||||
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
|
||||
.inject_touch_event = {
|
||||
.action = event->buttons_state ? AMOTION_EVENT_ACTION_MOVE
|
||||
: AMOTION_EVENT_ACTION_HOVER_MOVE,
|
||||
.action = AMOTION_EVENT_ACTION_MOVE,
|
||||
.pointer_id = event->pointer_id,
|
||||
.position = event->position,
|
||||
.pressure = 1.f,
|
||||
@ -146,10 +145,8 @@ sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
|
||||
}
|
||||
|
||||
void
|
||||
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller,
|
||||
bool mouse_hover) {
|
||||
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller) {
|
||||
m->controller = controller;
|
||||
m->mouse_hover = mouse_hover;
|
||||
|
||||
static const struct sc_mouse_processor_ops ops = {
|
||||
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
|
||||
|
@ -13,11 +13,9 @@ struct sc_mouse_sdk {
|
||||
struct sc_mouse_processor mouse_processor; // mouse processor trait
|
||||
|
||||
struct sc_controller *controller;
|
||||
bool mouse_hover;
|
||||
};
|
||||
|
||||
void
|
||||
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller,
|
||||
bool mouse_hover);
|
||||
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller);
|
||||
|
||||
#endif
|
||||
|
@ -23,20 +23,6 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.record_format = SC_RECORD_FORMAT_AUTO,
|
||||
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO,
|
||||
.mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO,
|
||||
.mouse_bindings = {
|
||||
.pri = {
|
||||
.right_click = SC_MOUSE_BINDING_AUTO,
|
||||
.middle_click = SC_MOUSE_BINDING_AUTO,
|
||||
.click4 = SC_MOUSE_BINDING_AUTO,
|
||||
.click5 = SC_MOUSE_BINDING_AUTO,
|
||||
},
|
||||
.sec = {
|
||||
.right_click = SC_MOUSE_BINDING_AUTO,
|
||||
.middle_click = SC_MOUSE_BINDING_AUTO,
|
||||
.click4 = SC_MOUSE_BINDING_AUTO,
|
||||
.click5 = SC_MOUSE_BINDING_AUTO,
|
||||
},
|
||||
},
|
||||
.camera_facing = SC_CAMERA_FACING_ANY,
|
||||
.port_range = {
|
||||
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
|
||||
@ -44,7 +30,10 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
},
|
||||
.tunnel_host = 0,
|
||||
.tunnel_port = 0,
|
||||
.shortcut_mods = SC_SHORTCUT_MOD_LALT | SC_SHORTCUT_MOD_LSUPER,
|
||||
.shortcut_mods = {
|
||||
.data = {SC_SHORTCUT_MOD_LALT, SC_SHORTCUT_MOD_LSUPER},
|
||||
.count = 2,
|
||||
},
|
||||
.max_size = 0,
|
||||
.video_bit_rate = 0,
|
||||
.audio_bit_rate = 0,
|
||||
@ -82,6 +71,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.force_adb_forward = false,
|
||||
.disable_screensaver = false,
|
||||
.forward_key_repeat = true,
|
||||
.forward_all_clicks = false,
|
||||
.legacy_paste = false,
|
||||
.power_off_on_close = false,
|
||||
.clipboard_autosync = true,
|
||||
@ -100,8 +90,6 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||
.camera_high_speed = false,
|
||||
.list = 0,
|
||||
.window = true,
|
||||
.mouse_hover = true,
|
||||
.audio_dup = false,
|
||||
};
|
||||
|
||||
enum sc_orientation
|
||||
|
@ -59,7 +59,6 @@ enum sc_audio_source {
|
||||
SC_AUDIO_SOURCE_AUTO, // OUTPUT for video DISPLAY, MIC for video CAMERA
|
||||
SC_AUDIO_SOURCE_OUTPUT,
|
||||
SC_AUDIO_SOURCE_MIC,
|
||||
SC_AUDIO_SOURCE_PLAYBACK,
|
||||
};
|
||||
|
||||
enum sc_camera_facing {
|
||||
@ -156,28 +155,6 @@ enum sc_mouse_input_mode {
|
||||
SC_MOUSE_INPUT_MODE_AOA,
|
||||
};
|
||||
|
||||
enum sc_mouse_binding {
|
||||
SC_MOUSE_BINDING_AUTO,
|
||||
SC_MOUSE_BINDING_DISABLED,
|
||||
SC_MOUSE_BINDING_CLICK,
|
||||
SC_MOUSE_BINDING_BACK,
|
||||
SC_MOUSE_BINDING_HOME,
|
||||
SC_MOUSE_BINDING_APP_SWITCH,
|
||||
SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL,
|
||||
};
|
||||
|
||||
struct sc_mouse_binding_set {
|
||||
enum sc_mouse_binding right_click;
|
||||
enum sc_mouse_binding middle_click;
|
||||
enum sc_mouse_binding click4;
|
||||
enum sc_mouse_binding click5;
|
||||
};
|
||||
|
||||
struct sc_mouse_bindings {
|
||||
struct sc_mouse_binding_set pri;
|
||||
struct sc_mouse_binding_set sec; // When Shift is pressed
|
||||
};
|
||||
|
||||
enum sc_key_inject_mode {
|
||||
// Inject special keys, letters and space as key events.
|
||||
// Inject numbers and punctuation as text events.
|
||||
@ -192,6 +169,8 @@ enum sc_key_inject_mode {
|
||||
SC_KEY_INJECT_MODE_RAW,
|
||||
};
|
||||
|
||||
#define SC_MAX_SHORTCUT_MODS 8
|
||||
|
||||
enum sc_shortcut_mod {
|
||||
SC_SHORTCUT_MOD_LCTRL = 1 << 0,
|
||||
SC_SHORTCUT_MOD_RCTRL = 1 << 1,
|
||||
@ -201,6 +180,11 @@ enum sc_shortcut_mod {
|
||||
SC_SHORTCUT_MOD_RSUPER = 1 << 5,
|
||||
};
|
||||
|
||||
struct sc_shortcut_mods {
|
||||
unsigned data[SC_MAX_SHORTCUT_MODS];
|
||||
unsigned count;
|
||||
};
|
||||
|
||||
struct sc_port_range {
|
||||
uint16_t first;
|
||||
uint16_t last;
|
||||
@ -231,12 +215,11 @@ struct scrcpy_options {
|
||||
enum sc_record_format record_format;
|
||||
enum sc_keyboard_input_mode keyboard_input_mode;
|
||||
enum sc_mouse_input_mode mouse_input_mode;
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
enum sc_camera_facing camera_facing;
|
||||
struct sc_port_range port_range;
|
||||
uint32_t tunnel_host;
|
||||
uint16_t tunnel_port;
|
||||
uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
|
||||
struct sc_shortcut_mods shortcut_mods;
|
||||
uint16_t max_size;
|
||||
uint32_t video_bit_rate;
|
||||
uint32_t audio_bit_rate;
|
||||
@ -274,6 +257,7 @@ struct scrcpy_options {
|
||||
bool force_adb_forward;
|
||||
bool disable_screensaver;
|
||||
bool forward_key_repeat;
|
||||
bool forward_all_clicks;
|
||||
bool legacy_paste;
|
||||
bool power_off_on_close;
|
||||
bool clipboard_autosync;
|
||||
@ -296,8 +280,6 @@ struct scrcpy_options {
|
||||
#define SC_OPTION_LIST_CAMERA_SIZES 0x8
|
||||
uint8_t list;
|
||||
bool window;
|
||||
bool mouse_hover;
|
||||
bool audio_dup;
|
||||
};
|
||||
|
||||
extern const struct scrcpy_options scrcpy_options_default;
|
||||
|
@ -21,7 +21,7 @@ sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
|
||||
receiver->acksync = NULL;
|
||||
receiver->uhid_devices = NULL;
|
||||
|
||||
assert(cbs && cbs->on_ended);
|
||||
assert(cbs && cbs->on_error);
|
||||
receiver->cbs = cbs;
|
||||
receiver->cbs_userdata = cbs_userdata;
|
||||
|
||||
@ -134,15 +134,12 @@ run_receiver(void *data) {
|
||||
static uint8_t buf[DEVICE_MSG_MAX_SIZE];
|
||||
size_t head = 0;
|
||||
|
||||
bool error = false;
|
||||
|
||||
for (;;) {
|
||||
assert(head < DEVICE_MSG_MAX_SIZE);
|
||||
ssize_t r = net_recv(receiver->control_socket, buf + head,
|
||||
DEVICE_MSG_MAX_SIZE - head);
|
||||
if (r <= 0) {
|
||||
LOGD("Receiver stopped");
|
||||
// device disconnected: keep error=false
|
||||
break;
|
||||
}
|
||||
|
||||
@ -150,7 +147,6 @@ run_receiver(void *data) {
|
||||
ssize_t consumed = process_msgs(receiver, buf, head);
|
||||
if (consumed == -1) {
|
||||
// an error occurred
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -161,7 +157,7 @@ run_receiver(void *data) {
|
||||
}
|
||||
}
|
||||
|
||||
receiver->cbs->on_ended(receiver, error, receiver->cbs_userdata);
|
||||
receiver->cbs->on_error(receiver, receiver->cbs_userdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ struct sc_receiver {
|
||||
};
|
||||
|
||||
struct sc_receiver_callbacks {
|
||||
void (*on_ended)(struct sc_receiver *receiver, bool error, void *userdata);
|
||||
void (*on_error)(struct sc_receiver *receiver, void *userdata);
|
||||
};
|
||||
|
||||
bool
|
||||
|
@ -269,18 +269,13 @@ sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer,
|
||||
}
|
||||
|
||||
static void
|
||||
sc_controller_on_ended(struct sc_controller *controller, bool error,
|
||||
void *userdata) {
|
||||
sc_controller_on_error(struct sc_controller *controller, void *userdata) {
|
||||
// Note: this function may be called twice, once from the controller thread
|
||||
// and once from the receiver thread
|
||||
(void) controller;
|
||||
(void) userdata;
|
||||
|
||||
if (error) {
|
||||
PUSH_EVENT(SC_EVENT_CONTROLLER_ERROR);
|
||||
} else {
|
||||
PUSH_EVENT(SC_EVENT_DEVICE_DISCONNECTED);
|
||||
}
|
||||
PUSH_EVENT(SC_EVENT_CONTROLLER_ERROR);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -394,7 +389,6 @@ scrcpy(struct scrcpy_options *options) {
|
||||
.display_id = options->display_id,
|
||||
.video = options->video,
|
||||
.audio = options->audio,
|
||||
.audio_dup = options->audio_dup,
|
||||
.show_touches = options->show_touches,
|
||||
.stay_awake = options->stay_awake,
|
||||
.video_codec_options = options->video_codec_options,
|
||||
@ -573,7 +567,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
|
||||
if (options->control) {
|
||||
static const struct sc_controller_callbacks controller_cbs = {
|
||||
.on_ended = sc_controller_on_ended,
|
||||
.on_error = sc_controller_on_error,
|
||||
};
|
||||
|
||||
if (!sc_controller_init(&s->controller, s->server.control_socket,
|
||||
@ -687,8 +681,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
}
|
||||
|
||||
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
|
||||
sc_mouse_sdk_init(&s->mouse_sdk, &s->controller,
|
||||
options->mouse_hover);
|
||||
sc_mouse_sdk_init(&s->mouse_sdk, &s->controller);
|
||||
mp = &s->mouse_sdk.mouse_processor;
|
||||
} else if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_UHID) {
|
||||
bool ok = sc_mouse_uhid_init(&s->mouse_uhid, &s->controller);
|
||||
@ -719,10 +712,10 @@ scrcpy(struct scrcpy_options *options) {
|
||||
.fp = fp,
|
||||
.kp = kp,
|
||||
.mp = mp,
|
||||
.mouse_bindings = options->mouse_bindings,
|
||||
.forward_all_clicks = options->forward_all_clicks,
|
||||
.legacy_paste = options->legacy_paste,
|
||||
.clipboard_autosync = options->clipboard_autosync,
|
||||
.shortcut_mods = options->shortcut_mods,
|
||||
.shortcut_mods = &options->shortcut_mods,
|
||||
.window_title = window_title,
|
||||
.always_on_top = options->always_on_top,
|
||||
.window_x = options->window_x,
|
||||
@ -736,20 +729,23 @@ scrcpy(struct scrcpy_options *options) {
|
||||
.start_fps_counter = options->start_fps_counter,
|
||||
};
|
||||
|
||||
if (!sc_screen_init(&s->screen, &screen_params)) {
|
||||
goto end;
|
||||
}
|
||||
screen_initialized = true;
|
||||
|
||||
struct sc_frame_source *src;
|
||||
if (options->video_playback) {
|
||||
struct sc_frame_source *src = &s->video_decoder.frame_source;
|
||||
src = &s->video_decoder.frame_source;
|
||||
if (options->display_buffer) {
|
||||
sc_delay_buffer_init(&s->display_buffer,
|
||||
options->display_buffer, true);
|
||||
sc_frame_source_add_sink(src, &s->display_buffer.frame_sink);
|
||||
src = &s->display_buffer.frame_source;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sc_screen_init(&s->screen, &screen_params)) {
|
||||
goto end;
|
||||
}
|
||||
screen_initialized = true;
|
||||
|
||||
if (options->video_playback) {
|
||||
sc_frame_source_add_sink(src, &s->screen.frame_sink);
|
||||
}
|
||||
}
|
||||
|
@ -481,7 +481,7 @@ sc_screen_init(struct sc_screen *screen,
|
||||
.screen = screen,
|
||||
.kp = params->kp,
|
||||
.mp = params->mp,
|
||||
.mouse_bindings = params->mouse_bindings,
|
||||
.forward_all_clicks = params->forward_all_clicks,
|
||||
.legacy_paste = params->legacy_paste,
|
||||
.clipboard_autosync = params->clipboard_autosync,
|
||||
.shortcut_mods = params->shortcut_mods,
|
||||
|
@ -79,10 +79,10 @@ struct sc_screen_params {
|
||||
struct sc_key_processor *kp;
|
||||
struct sc_mouse_processor *mp;
|
||||
|
||||
struct sc_mouse_bindings mouse_bindings;
|
||||
bool forward_all_clicks;
|
||||
bool legacy_paste;
|
||||
bool clipboard_autosync;
|
||||
uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
|
||||
const struct sc_shortcut_mods *shortcut_mods;
|
||||
|
||||
const char *window_title;
|
||||
bool always_on_top;
|
||||
|
@ -147,7 +147,7 @@ log_level_to_server_string(enum sc_log_level level) {
|
||||
return "error";
|
||||
default:
|
||||
assert(!"unexpected log level");
|
||||
return NULL;
|
||||
return "(unknown)";
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,7 +183,6 @@ sc_server_get_codec_name(enum sc_codec codec) {
|
||||
case SC_CODEC_RAW:
|
||||
return "raw";
|
||||
default:
|
||||
assert(!"unexpected codec");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@ -198,22 +197,6 @@ sc_server_get_camera_facing_name(enum sc_camera_facing camera_facing) {
|
||||
case SC_CAMERA_FACING_EXTERNAL:
|
||||
return "external";
|
||||
default:
|
||||
assert(!"unexpected camera facing");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *
|
||||
sc_server_get_audio_source_name(enum sc_audio_source audio_source) {
|
||||
switch (audio_source) {
|
||||
case SC_AUDIO_SOURCE_OUTPUT:
|
||||
return "output";
|
||||
case SC_AUDIO_SOURCE_MIC:
|
||||
return "mic";
|
||||
case SC_AUDIO_SOURCE_PLAYBACK:
|
||||
return "playback";
|
||||
default:
|
||||
assert(!"unexpected audio source");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@ -288,14 +271,8 @@ execute_server(struct sc_server *server,
|
||||
assert(params->video_source == SC_VIDEO_SOURCE_CAMERA);
|
||||
ADD_PARAM("video_source=camera");
|
||||
}
|
||||
// If audio is enabled, an "auto" audio source must have been resolved
|
||||
assert(params->audio_source != SC_AUDIO_SOURCE_AUTO || !params->audio);
|
||||
if (params->audio_source != SC_AUDIO_SOURCE_OUTPUT && params->audio) {
|
||||
ADD_PARAM("audio_source=%s",
|
||||
sc_server_get_audio_source_name(params->audio_source));
|
||||
}
|
||||
if (params->audio_dup) {
|
||||
ADD_PARAM("audio_dup=true");
|
||||
if (params->audio_source == SC_AUDIO_SOURCE_MIC) {
|
||||
ADD_PARAM("audio_source=mic");
|
||||
}
|
||||
if (params->max_size) {
|
||||
ADD_PARAM("max_size=%" PRIu16, params->max_size);
|
||||
|
@ -50,7 +50,6 @@ struct sc_server_params {
|
||||
uint32_t display_id;
|
||||
bool video;
|
||||
bool audio;
|
||||
bool audio_dup;
|
||||
bool show_touches;
|
||||
bool stay_awake;
|
||||
bool force_adb_forward;
|
||||
|
@ -169,7 +169,7 @@ sc_screen_otg_process_mouse_motion(struct sc_screen_otg *screen,
|
||||
// .position not used for HID events
|
||||
.xrel = event->xrel,
|
||||
.yrel = event->yrel,
|
||||
.buttons_state = sc_mouse_buttons_state_from_sdl(event->state),
|
||||
.buttons_state = sc_mouse_buttons_state_from_sdl(event->state, true),
|
||||
};
|
||||
|
||||
assert(mp->ops->process_mouse_motion);
|
||||
@ -188,7 +188,8 @@ sc_screen_otg_process_mouse_button(struct sc_screen_otg *screen,
|
||||
// .position not used for HID events
|
||||
.action = sc_action_from_sdl_mousebutton_type(event->type),
|
||||
.button = sc_mouse_button_from_sdl(event->button),
|
||||
.buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state),
|
||||
.buttons_state =
|
||||
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, true),
|
||||
};
|
||||
|
||||
assert(mp->ops->process_mouse_click);
|
||||
@ -207,7 +208,8 @@ sc_screen_otg_process_mouse_wheel(struct sc_screen_otg *screen,
|
||||
// .position not used for HID events
|
||||
.hscroll = event->x,
|
||||
.vscroll = event->y,
|
||||
.buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state),
|
||||
.buttons_state =
|
||||
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, true),
|
||||
};
|
||||
|
||||
assert(mp->ops->process_mouse_scroll);
|
||||
|
@ -240,7 +240,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) {
|
||||
vs->frame = av_frame_alloc();
|
||||
if (!vs->frame) {
|
||||
LOG_OOM();
|
||||
goto error_avcodec_free_context;
|
||||
goto error_avcodec_close;
|
||||
}
|
||||
|
||||
vs->packet = av_packet_alloc();
|
||||
@ -268,6 +268,8 @@ error_av_packet_free:
|
||||
av_packet_free(&vs->packet);
|
||||
error_av_frame_free:
|
||||
av_frame_free(&vs->frame);
|
||||
error_avcodec_close:
|
||||
avcodec_close(vs->encoder_ctx);
|
||||
error_avcodec_free_context:
|
||||
avcodec_free_context(&vs->encoder_ctx);
|
||||
error_avio_close:
|
||||
@ -295,6 +297,7 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
|
||||
|
||||
av_packet_free(&vs->packet);
|
||||
av_frame_free(&vs->frame);
|
||||
avcodec_close(vs->encoder_ctx);
|
||||
avcodec_free_context(&vs->encoder_ctx);
|
||||
avio_close(vs->format_ctx->pb);
|
||||
avformat_free_context(vs->format_ctx);
|
||||
|
@ -124,22 +124,32 @@ static void test_options2(void) {
|
||||
}
|
||||
|
||||
static void test_parse_shortcut_mods(void) {
|
||||
uint8_t mods;
|
||||
struct sc_shortcut_mods mods;
|
||||
bool ok;
|
||||
|
||||
ok = sc_parse_shortcut_mods("lctrl", &mods);
|
||||
assert(ok);
|
||||
assert(mods == SC_SHORTCUT_MOD_LCTRL);
|
||||
assert(mods.count == 1);
|
||||
assert(mods.data[0] == SC_SHORTCUT_MOD_LCTRL);
|
||||
|
||||
ok = sc_parse_shortcut_mods("lctrl+lalt", &mods);
|
||||
assert(ok);
|
||||
assert(mods.count == 1);
|
||||
assert(mods.data[0] == (SC_SHORTCUT_MOD_LCTRL | SC_SHORTCUT_MOD_LALT));
|
||||
|
||||
ok = sc_parse_shortcut_mods("rctrl,lalt", &mods);
|
||||
assert(ok);
|
||||
assert(mods == (SC_SHORTCUT_MOD_RCTRL | SC_SHORTCUT_MOD_LALT));
|
||||
assert(mods.count == 2);
|
||||
assert(mods.data[0] == SC_SHORTCUT_MOD_RCTRL);
|
||||
assert(mods.data[1] == SC_SHORTCUT_MOD_LALT);
|
||||
|
||||
ok = sc_parse_shortcut_mods("lsuper,rsuper,lctrl", &mods);
|
||||
ok = sc_parse_shortcut_mods("lsuper,rsuper+lalt,lctrl+rctrl+ralt", &mods);
|
||||
assert(ok);
|
||||
assert(mods == (SC_SHORTCUT_MOD_LSUPER
|
||||
| SC_SHORTCUT_MOD_RSUPER
|
||||
| SC_SHORTCUT_MOD_LCTRL));
|
||||
assert(mods.count == 3);
|
||||
assert(mods.data[0] == SC_SHORTCUT_MOD_LSUPER);
|
||||
assert(mods.data[1] == (SC_SHORTCUT_MOD_RSUPER | SC_SHORTCUT_MOD_LALT));
|
||||
assert(mods.data[2] == (SC_SHORTCUT_MOD_LCTRL | SC_SHORTCUT_MOD_RCTRL |
|
||||
SC_SHORTCUT_MOD_RALT));
|
||||
|
||||
ok = sc_parse_shortcut_mods("", &mods);
|
||||
assert(!ok);
|
||||
|
@ -7,7 +7,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.3.0'
|
||||
classpath 'com.android.tools.build:gradle:8.1.3'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
26
doc/audio.md
26
doc/audio.md
@ -42,7 +42,7 @@ scrcpy --no-window
|
||||
# interrupt with Ctrl+C
|
||||
```
|
||||
|
||||
Without video, the audio latency is typically not critical, so it might be
|
||||
Without video, the audio latency is typically not criticial, so it might be
|
||||
interesting to add [buffering](#buffering) to minimize glitches:
|
||||
|
||||
```
|
||||
@ -66,30 +66,6 @@ the computer:
|
||||
scrcpy --audio-source=mic --no-video --no-playback --record=file.opus
|
||||
```
|
||||
|
||||
### Duplication
|
||||
|
||||
An alternative device audio capture method is also available (only for Android
|
||||
13 and above):
|
||||
|
||||
```
|
||||
scrcpy --audio-source=playback
|
||||
```
|
||||
|
||||
This audio source supports keeping the audio playing on the device while
|
||||
mirroring, with `--audio-dup`:
|
||||
|
||||
```bash
|
||||
scrcpy --audio-source=playback --audio-dup
|
||||
# or simply:
|
||||
scrcpy --audio-dup # --audio-source=playback is implied
|
||||
```
|
||||
|
||||
However, it requires Android 13, and Android apps can opt-out (so they are not
|
||||
captured).
|
||||
|
||||
|
||||
See [#4380](https://github.com/Genymobile/scrcpy/issues/4380).
|
||||
|
||||
|
||||
## Codec
|
||||
|
||||
|
@ -94,7 +94,7 @@ This is the preferred method (and the way the release is built).
|
||||
From _Debian_, install _mingw_:
|
||||
|
||||
```bash
|
||||
sudo apt install mingw-w64 mingw-w64-tools libz-mingw-w64-dev
|
||||
sudo apt install mingw-w64 mingw-w64-tools
|
||||
```
|
||||
|
||||
You also need the JDK to build the server:
|
||||
@ -233,10 +233,10 @@ install` must be run as root)._
|
||||
|
||||
#### Option 2: Use prebuilt server
|
||||
|
||||
- [`scrcpy-server-v2.6`][direct-scrcpy-server]
|
||||
<sub>SHA-256: `7b723ff79a27f14e6ebaaaae7ef9548c40651c94e64d178612b13adf7158eb2e`</sub>
|
||||
- [`scrcpy-server-v2.4`][direct-scrcpy-server]
|
||||
<sub>SHA-256: `93c272b7438605c055e127f7444064ed78fa9ca49f81156777fd201e79ce7ba3`</sub>
|
||||
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.6/scrcpy-server-v2.6
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-server-v2.4
|
||||
|
||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||
configuration:
|
||||
|
@ -106,6 +106,15 @@ only inverts _x_.
|
||||
This only works for the default mouse mode (`--mouse=sdk`).
|
||||
|
||||
|
||||
## Right-click and middle-click
|
||||
|
||||
By default, right-click triggers BACK (or POWER on) and middle-click triggers
|
||||
HOME. To disable these shortcuts and forward the clicks to the device instead:
|
||||
|
||||
```bash
|
||||
scrcpy --forward-all-clicks
|
||||
```
|
||||
|
||||
## File drop
|
||||
|
||||
### Install APK
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
Scrcpy is packaged in several distributions and package managers:
|
||||
|
||||
- Debian/Ubuntu: ~~`apt install scrcpy`~~ _(obsolete version)_
|
||||
- Debian/Ubuntu: `apt install scrcpy`
|
||||
- Arch Linux: `pacman -S scrcpy`
|
||||
- Fedora: `dnf copr enable zeno/scrcpy && dnf install scrcpy`
|
||||
- Gentoo: `emerge scrcpy`
|
||||
|
74
doc/mouse.md
74
doc/mouse.md
@ -18,14 +18,6 @@ Note that on some devices, an additional option must be enabled in developer
|
||||
options for this mouse mode to work. See
|
||||
[prerequisites](/README.md#prerequisites).
|
||||
|
||||
### Mouse hover
|
||||
|
||||
By default, mouse hover (mouse motion without any clicks) events are forwarded
|
||||
to the device. This can be disabled with:
|
||||
|
||||
```
|
||||
scrcpy --no-mouse-hover
|
||||
```
|
||||
|
||||
## Physical mouse simulation
|
||||
|
||||
@ -76,69 +68,3 @@ debugging disabled (see [OTG](otg.md)).
|
||||
Note: On Windows, it may only work in [OTG mode](otg.md), not while mirroring
|
||||
(it is not possible to open a USB device if it is already open by another
|
||||
process like the _adb daemon_).
|
||||
|
||||
|
||||
## Mouse bindings
|
||||
|
||||
By default, with SDK mouse:
|
||||
- right-click triggers BACK (or POWER on)
|
||||
- middle-click triggers HOME
|
||||
- the 4th click triggers APP_SWITCH
|
||||
- the 5th click expands the notification panel
|
||||
|
||||
The secondary clicks may be forwarded to the device instead by pressing the
|
||||
<kbd>Shift</kbd> key (e.g. <kbd>Shift</kbd>+right-click injects a right click to
|
||||
the device).
|
||||
|
||||
In AOA and UHID mouse modes, the default bindings are reversed: all clicks are
|
||||
forwarded by default, and pressing <kbd>Shift</kbd> gives access to the
|
||||
shortcuts (since the cursor is handled on the device side, it makes more sense
|
||||
to forward all mouse buttons by default in these modes).
|
||||
|
||||
The shortcuts can be configured using `--mouse-bind=xxxx:xxxx` for any mouse
|
||||
mode. The argument must be one or two sequences (separated by `:`) of exactly 4
|
||||
characters, one for each secondary click:
|
||||
|
||||
```
|
||||
.---- Shift + right click
|
||||
SECONDARY |.--- Shift + middle click
|
||||
BINDINGS ||.-- Shift + 4th click
|
||||
|||.- Shift + 5th click
|
||||
||||
|
||||
vvvv
|
||||
--mouse-bind=xxxx:xxxx
|
||||
^^^^
|
||||
||||
|
||||
PRIMARY ||| `- 5th click
|
||||
BINDINGS || `-- 4th click
|
||||
| `--- middle click
|
||||
`---- right click
|
||||
```
|
||||
|
||||
Each character must be one of the following:
|
||||
|
||||
- `+`: forward the click to the device
|
||||
- `-`: ignore the click
|
||||
- `b`: trigger shortcut BACK (or turn screen on if off)
|
||||
- `h`: trigger shortcut HOME
|
||||
- `s`: trigger shortcut APP_SWITCH
|
||||
- `n`: trigger shortcut "expand notification panel"
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
scrcpy --mouse-bind=bhsn:++++ # the default mode for SDK mouse
|
||||
scrcpy --mouse-bind=++++:bhsn # the default mode for AOA and UHID
|
||||
scrcpy --mouse-bind=++bh:++sn # forward right and middle clicks,
|
||||
# use 4th and 5th for BACK and HOME,
|
||||
# use Shift+4th and Shift+5th for APP_SWITCH
|
||||
# and expand notification panel
|
||||
```
|
||||
|
||||
The second sequence of bindings may be omitted. In that case, it is the same as
|
||||
the first one:
|
||||
|
||||
```bash
|
||||
scrcpy --mouse-bind=bhsn
|
||||
scrcpy --mouse-bind=bhsn:bhsn # equivalent
|
||||
```
|
||||
|
@ -13,8 +13,8 @@ It can be changed using `--shortcut-mod`. Possible keys are `lctrl`, `rctrl`,
|
||||
# use RCtrl for shortcuts
|
||||
scrcpy --shortcut-mod=rctrl
|
||||
|
||||
# use either LCtrl or LSuper for shortcuts
|
||||
scrcpy --shortcut-mod=lctrl,lsuper
|
||||
# use either LCtrl+LAlt or LSuper for shortcuts
|
||||
scrcpy --shortcut-mod=lctrl+lalt,lsuper
|
||||
```
|
||||
|
||||
_<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
||||
|
@ -4,18 +4,24 @@
|
||||
|
||||
Download the [latest release]:
|
||||
|
||||
- [`scrcpy-win64-v2.6.zip`][direct-win64] (64-bit)
|
||||
<sub>SHA-256: `3d490a72997af950aec0540e28627ada35c8226bc9774500014c9697d9b53194`</sub>
|
||||
- [`scrcpy-win32-v2.6.zip`][direct-win32] (32-bit)
|
||||
<sub>SHA-256: `6c68f6b31ddef5ed61a7546f423bd4fc99d568eb4c4e3409e0df496187eb3783`</sub>
|
||||
- [`scrcpy-win64-v2.4.zip`][direct-win64] (64-bit)
|
||||
<sub>SHA-256: `9dc56f21bfa455352ec0c58b40feaf2fb02d67372910a4235e298ece286ff3a9`</sub>
|
||||
- [`scrcpy-win32-v2.4.zip`][direct-win32] (32-bit)
|
||||
<sub>SHA-256: `cf92acc45eef37c6ee2db819f92e420ced3bc50f1348dd57f7d6ca1fc80f6116`</sub>
|
||||
|
||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.6/scrcpy-win64-v2.6.zip
|
||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.6/scrcpy-win32-v2.6.zip
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-win64-v2.4.zip
|
||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-win32-v2.4.zip
|
||||
|
||||
and extract it.
|
||||
|
||||
Alternatively, you could install it from packages manager, like [Chocolatey]:
|
||||
Alternatively, you could install it from packages manager, like [Winget]:
|
||||
|
||||
```bash
|
||||
winget install scrcpy
|
||||
```
|
||||
|
||||
or [Chocolatey]:
|
||||
|
||||
```bash
|
||||
choco install scrcpy
|
||||
|
@ -2,8 +2,8 @@
|
||||
set -e
|
||||
|
||||
BUILDDIR=build-auto
|
||||
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.6/scrcpy-server-v2.6
|
||||
PREBUILT_SERVER_SHA256=7b723ff79a27f14e6ebaaaae7ef9548c40651c94e64d178612b13adf7158eb2e
|
||||
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-server-v2.4
|
||||
PREBUILT_SERVER_SHA256=93c272b7438605c055e127f7444064ed78fa9ca49f81156777fd201e79ce7ba3
|
||||
|
||||
echo "[scrcpy] Downloading prebuilt server..."
|
||||
wget "$PREBUILT_SERVER_URL" -O scrcpy-server
|
||||
|
@ -1,5 +1,5 @@
|
||||
project('scrcpy', 'c',
|
||||
version: '2.6',
|
||||
version: '2.4',
|
||||
meson_version: '>= 0.48',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
|
@ -24,7 +24,7 @@ SERVER_BUILD_DIR := build-server
|
||||
WIN32_BUILD_DIR := build-win32
|
||||
WIN64_BUILD_DIR := build-win64
|
||||
|
||||
VERSION := $(shell git describe --tags --exclude='*install-release' --always)
|
||||
VERSION := $(shell git describe --tags --always)
|
||||
|
||||
DIST := dist
|
||||
WIN32_TARGET_DIR := scrcpy-win32-$(VERSION)
|
||||
|
@ -7,8 +7,8 @@ android {
|
||||
applicationId "com.genymobile.scrcpy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
versionCode 20600
|
||||
versionName "2.6"
|
||||
versionCode 20400
|
||||
versionName "2.4"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
|
@ -12,7 +12,7 @@
|
||||
set -e
|
||||
|
||||
SCRCPY_DEBUG=false
|
||||
SCRCPY_VERSION_NAME=2.6
|
||||
SCRCPY_VERSION_NAME=2.4
|
||||
|
||||
PLATFORM=${ANDROID_PLATFORM:-34}
|
||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0}
|
||||
@ -50,29 +50,14 @@ cd "$SERVER_DIR/src/main/aidl"
|
||||
android/content/IOnPrimaryClipChangedListener.aidl
|
||||
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" android/view/IDisplayFoldListener.aidl
|
||||
|
||||
SRC=( \
|
||||
com/genymobile/scrcpy/*.java \
|
||||
com/genymobile/scrcpy/audio/*.java \
|
||||
com/genymobile/scrcpy/control/*.java \
|
||||
com/genymobile/scrcpy/device/*.java \
|
||||
com/genymobile/scrcpy/util/*.java \
|
||||
com/genymobile/scrcpy/video/*.java \
|
||||
com/genymobile/scrcpy/wrappers/*.java \
|
||||
)
|
||||
|
||||
CLASSES=()
|
||||
for src in "${SRC[@]}"
|
||||
do
|
||||
CLASSES+=("${src%.java}.class")
|
||||
done
|
||||
|
||||
echo "Compiling java sources..."
|
||||
cd ../java
|
||||
javac -bootclasspath "$ANDROID_JAR" \
|
||||
-cp "$LAMBDA_JAR:$GEN_DIR" \
|
||||
-d "$CLASSES_DIR" \
|
||||
-source 1.8 -target 1.8 \
|
||||
${SRC[@]}
|
||||
com/genymobile/scrcpy/*.java \
|
||||
com/genymobile/scrcpy/wrappers/*.java
|
||||
|
||||
echo "Dexing..."
|
||||
cd "$CLASSES_DIR"
|
||||
@ -83,7 +68,8 @@ then
|
||||
"$BUILD_TOOLS_DIR/dx" --dex --output "$BUILD_DIR/classes.dex" \
|
||||
android/view/*.class \
|
||||
android/content/*.class \
|
||||
${CLASSES[@]}
|
||||
com/genymobile/scrcpy/*.class \
|
||||
com/genymobile/scrcpy/wrappers/*.class
|
||||
|
||||
echo "Archiving..."
|
||||
cd "$BUILD_DIR"
|
||||
@ -95,7 +81,8 @@ else
|
||||
--output "$BUILD_DIR/classes.zip" \
|
||||
android/view/*.class \
|
||||
android/content/*.class \
|
||||
${CLASSES[@]}
|
||||
com/genymobile/scrcpy/*.class \
|
||||
com/genymobile/scrcpy/wrappers/*.class
|
||||
|
||||
cd "$BUILD_DIR"
|
||||
mv classes.zip "$SERVER_BINARY"
|
||||
|
@ -1,48 +1,55 @@
|
||||
package com.genymobile.scrcpy.audio;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.FakeContext;
|
||||
import com.genymobile.scrcpy.Workarounds;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.AudioTimestamp;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class AudioDirectCapture implements AudioCapture {
|
||||
public final class AudioCapture {
|
||||
|
||||
private static final int SAMPLE_RATE = AudioConfig.SAMPLE_RATE;
|
||||
private static final int CHANNEL_CONFIG = AudioConfig.CHANNEL_CONFIG;
|
||||
private static final int CHANNELS = AudioConfig.CHANNELS;
|
||||
private static final int CHANNEL_MASK = AudioConfig.CHANNEL_MASK;
|
||||
private static final int ENCODING = AudioConfig.ENCODING;
|
||||
public static final int SAMPLE_RATE = 48000;
|
||||
public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_STEREO;
|
||||
public static final int CHANNELS = 2;
|
||||
public static final int CHANNEL_MASK = AudioFormat.CHANNEL_IN_LEFT | AudioFormat.CHANNEL_IN_RIGHT;
|
||||
public static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;
|
||||
public static final int BYTES_PER_SAMPLE = 2;
|
||||
|
||||
// Never read more than 1024 samples, even if the buffer is bigger (that would increase latency).
|
||||
// A lower value is useless, since the system captures audio samples by blocks of 1024 (so for example if we read by blocks of 256 samples, we
|
||||
// receive 4 successive blocks without waiting, then we wait for the 4 next ones).
|
||||
public static final int MAX_READ_SIZE = 1024 * CHANNELS * BYTES_PER_SAMPLE;
|
||||
|
||||
private static final long ONE_SAMPLE_US = (1000000 + SAMPLE_RATE - 1) / SAMPLE_RATE; // 1 sample in microseconds (used for fixing PTS)
|
||||
|
||||
private final int audioSource;
|
||||
|
||||
private AudioRecord recorder;
|
||||
private AudioRecordReader reader;
|
||||
|
||||
public AudioDirectCapture(AudioSource audioSource) {
|
||||
this.audioSource = getAudioSourceValue(audioSource);
|
||||
private final AudioTimestamp timestamp = new AudioTimestamp();
|
||||
private long previousRecorderTimestamp = -1;
|
||||
private long previousPts = 0;
|
||||
private long nextPts = 0;
|
||||
|
||||
public AudioCapture(AudioSource audioSource) {
|
||||
this.audioSource = audioSource.value();
|
||||
}
|
||||
|
||||
private static int getAudioSourceValue(AudioSource audioSource) {
|
||||
switch (audioSource) {
|
||||
case OUTPUT:
|
||||
return MediaRecorder.AudioSource.REMOTE_SUBMIX;
|
||||
case MIC:
|
||||
return MediaRecorder.AudioSource.MIC;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported audio source: " + audioSource);
|
||||
}
|
||||
private static AudioFormat createAudioFormat() {
|
||||
AudioFormat.Builder builder = new AudioFormat.Builder();
|
||||
builder.setEncoding(ENCODING);
|
||||
builder.setSampleRate(SAMPLE_RATE);
|
||||
builder.setChannelMask(CHANNEL_CONFIG);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
@ -54,7 +61,7 @@ public class AudioDirectCapture implements AudioCapture {
|
||||
builder.setContext(FakeContext.get());
|
||||
}
|
||||
builder.setAudioSource(audioSource);
|
||||
builder.setAudioFormat(AudioConfig.createAudioFormat());
|
||||
builder.setAudioFormat(createAudioFormat());
|
||||
int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, ENCODING);
|
||||
// This buffer size does not impact latency
|
||||
builder.setBufferSizeInBytes(8 * minBufferSize);
|
||||
@ -79,7 +86,7 @@ public class AudioDirectCapture implements AudioCapture {
|
||||
ServiceManager.getActivityManager().forceStopPackage(FakeContext.PACKAGE_NAME);
|
||||
}
|
||||
|
||||
private void tryStartRecording(int attempts, int delayMs) throws AudioCaptureException {
|
||||
private void tryStartRecording(int attempts, int delayMs) throws AudioCaptureForegroundException {
|
||||
while (attempts-- > 0) {
|
||||
// Wait for activity to start
|
||||
SystemClock.sleep(delayMs);
|
||||
@ -91,7 +98,7 @@ public class AudioDirectCapture implements AudioCapture {
|
||||
Ln.e("Failed to start audio capture");
|
||||
Ln.e("On Android 11, audio capture must be started in the foreground, make sure that the device is unlocked when starting "
|
||||
+ "scrcpy.");
|
||||
throw new AudioCaptureException();
|
||||
throw new AudioCaptureForegroundException();
|
||||
} else {
|
||||
Ln.d("Failed to start audio capture, retrying...");
|
||||
}
|
||||
@ -99,7 +106,7 @@ public class AudioDirectCapture implements AudioCapture {
|
||||
}
|
||||
}
|
||||
|
||||
private void startRecording() throws AudioCaptureException {
|
||||
private void startRecording() {
|
||||
try {
|
||||
recorder = createAudioRecord(audioSource);
|
||||
} catch (NullPointerException e) {
|
||||
@ -109,19 +116,9 @@ public class AudioDirectCapture implements AudioCapture {
|
||||
recorder = Workarounds.createAudioRecord(audioSource, SAMPLE_RATE, CHANNEL_CONFIG, CHANNELS, CHANNEL_MASK, ENCODING);
|
||||
}
|
||||
recorder.startRecording();
|
||||
reader = new AudioRecordReader(recorder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkCompatibility() throws AudioCaptureException {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
Ln.w("Audio disabled: it is not supported before Android 11");
|
||||
throw new AudioCaptureException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws AudioCaptureException {
|
||||
public void start() throws AudioCaptureForegroundException {
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||
startWorkaroundAndroid11();
|
||||
try {
|
||||
@ -134,7 +131,6 @@ public class AudioDirectCapture implements AudioCapture {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (recorder != null) {
|
||||
// Will call .stop() if necessary, without throwing an IllegalStateException
|
||||
@ -142,9 +138,42 @@ public class AudioDirectCapture implements AudioCapture {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
public int read(ByteBuffer outDirectBuffer, MediaCodec.BufferInfo outBufferInfo) {
|
||||
return reader.read(outDirectBuffer, outBufferInfo);
|
||||
public int read(ByteBuffer directBuffer, MediaCodec.BufferInfo outBufferInfo) {
|
||||
int r = recorder.read(directBuffer, MAX_READ_SIZE);
|
||||
if (r <= 0) {
|
||||
return r;
|
||||
}
|
||||
|
||||
long pts;
|
||||
|
||||
int ret = recorder.getTimestamp(timestamp, AudioTimestamp.TIMEBASE_MONOTONIC);
|
||||
if (ret == AudioRecord.SUCCESS && timestamp.nanoTime != previousRecorderTimestamp) {
|
||||
pts = timestamp.nanoTime / 1000;
|
||||
previousRecorderTimestamp = timestamp.nanoTime;
|
||||
} else {
|
||||
if (nextPts == 0) {
|
||||
Ln.w("Could not get initial audio timestamp");
|
||||
nextPts = System.nanoTime() / 1000;
|
||||
}
|
||||
// compute from previous timestamp and packet size
|
||||
pts = nextPts;
|
||||
}
|
||||
|
||||
long durationUs = r * 1000000L / (CHANNELS * BYTES_PER_SAMPLE * SAMPLE_RATE);
|
||||
nextPts = pts + durationUs;
|
||||
|
||||
if (previousPts != 0 && pts < previousPts + ONE_SAMPLE_US) {
|
||||
// Audio PTS may come from two sources:
|
||||
// - recorder.getTimestamp() if the call works;
|
||||
// - an estimation from the previous PTS and the packet size as a fallback.
|
||||
//
|
||||
// Therefore, the property that PTS are monotonically increasing is no guaranteed in corner cases, so enforce it.
|
||||
pts = previousPts + ONE_SAMPLE_US;
|
||||
}
|
||||
previousPts = pts;
|
||||
|
||||
outBufferInfo.set(0, r, pts, 0);
|
||||
return r;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
/**
|
||||
* Exception thrown if audio capture failed on Android 11 specifically because the running App (shell) was not in foreground.
|
||||
*/
|
||||
public class AudioCaptureForegroundException extends Exception {
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
package com.genymobile.scrcpy.audio;
|
||||
|
||||
import com.genymobile.scrcpy.util.Codec;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.media.MediaFormat;
|
||||
|
@ -1,14 +1,4 @@
|
||||
package com.genymobile.scrcpy.audio;
|
||||
|
||||
import com.genymobile.scrcpy.AsyncProcessor;
|
||||
import com.genymobile.scrcpy.util.Codec;
|
||||
import com.genymobile.scrcpy.util.CodecOption;
|
||||
import com.genymobile.scrcpy.util.CodecUtils;
|
||||
import com.genymobile.scrcpy.device.ConfigurationException;
|
||||
import com.genymobile.scrcpy.util.IO;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.util.LogUtils;
|
||||
import com.genymobile.scrcpy.device.Streamer;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.media.MediaCodec;
|
||||
@ -44,8 +34,8 @@ public final class AudioEncoder implements AsyncProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private static final int SAMPLE_RATE = AudioConfig.SAMPLE_RATE;
|
||||
private static final int CHANNELS = AudioConfig.CHANNELS;
|
||||
private static final int SAMPLE_RATE = AudioCapture.SAMPLE_RATE;
|
||||
private static final int CHANNELS = AudioCapture.CHANNELS;
|
||||
|
||||
private final AudioCapture capture;
|
||||
private final Streamer streamer;
|
||||
@ -132,7 +122,7 @@ public final class AudioEncoder implements AsyncProcessor {
|
||||
} catch (ConfigurationException e) {
|
||||
// Do not print stack trace, a user-friendly error-message has already been logged
|
||||
fatalError = true;
|
||||
} catch (AudioCaptureException e) {
|
||||
} catch (AudioCaptureForegroundException e) {
|
||||
// Do not print stack trace, a user-friendly error-message has already been logged
|
||||
} catch (IOException e) {
|
||||
Ln.e("Audio encoding error", e);
|
||||
@ -176,7 +166,7 @@ public final class AudioEncoder implements AsyncProcessor {
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
private void encode() throws IOException, ConfigurationException, AudioCaptureException {
|
||||
public void encode() throws IOException, ConfigurationException, AudioCaptureForegroundException {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
Ln.w("Audio disabled: it is not supported before Android 11");
|
||||
streamer.writeDisableStream(false);
|
||||
@ -187,8 +177,6 @@ public final class AudioEncoder implements AsyncProcessor {
|
||||
|
||||
boolean mediaCodecStarted = false;
|
||||
try {
|
||||
capture.checkCompatibility(); // throws an AudioCaptureException on error
|
||||
|
||||
Codec codec = streamer.getCodec();
|
||||
mediaCodec = createMediaCodec(codec, encoderName);
|
||||
|
@ -1,9 +1,4 @@
|
||||
package com.genymobile.scrcpy.audio;
|
||||
|
||||
import com.genymobile.scrcpy.AsyncProcessor;
|
||||
import com.genymobile.scrcpy.util.IO;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.device.Streamer;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.os.Build;
|
||||
@ -23,14 +18,14 @@ public final class AudioRawRecorder implements AsyncProcessor {
|
||||
this.streamer = streamer;
|
||||
}
|
||||
|
||||
private void record() throws IOException, AudioCaptureException {
|
||||
private void record() throws IOException, AudioCaptureForegroundException {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
Ln.w("Audio disabled: it is not supported before Android 11");
|
||||
streamer.writeDisableStream(false);
|
||||
return;
|
||||
}
|
||||
|
||||
final ByteBuffer buffer = ByteBuffer.allocateDirect(AudioConfig.MAX_READ_SIZE);
|
||||
final ByteBuffer buffer = ByteBuffer.allocateDirect(AudioCapture.MAX_READ_SIZE);
|
||||
final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||
|
||||
try {
|
||||
@ -69,7 +64,7 @@ public final class AudioRawRecorder implements AsyncProcessor {
|
||||
boolean fatalError = false;
|
||||
try {
|
||||
record();
|
||||
} catch (AudioCaptureException e) {
|
||||
} catch (AudioCaptureForegroundException e) {
|
||||
// Do not print stack trace, a user-friendly error-message has already been logged
|
||||
} catch (Throwable t) {
|
||||
Ln.e("Audio recording error", t);
|
30
server/src/main/java/com/genymobile/scrcpy/AudioSource.java
Normal file
30
server/src/main/java/com/genymobile/scrcpy/AudioSource.java
Normal file
@ -0,0 +1,30 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.media.MediaRecorder;
|
||||
|
||||
public enum AudioSource {
|
||||
OUTPUT("output", MediaRecorder.AudioSource.REMOTE_SUBMIX),
|
||||
MIC("mic", MediaRecorder.AudioSource.MIC);
|
||||
|
||||
private final String name;
|
||||
private final int value;
|
||||
|
||||
AudioSource(String name, int value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
int value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
static AudioSource findByName(String name) {
|
||||
for (AudioSource audioSource : AudioSource.values()) {
|
||||
if (name.equals(audioSource.name)) {
|
||||
return audioSource;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.util;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
public final class Binary {
|
||||
private Binary() {
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.video;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
public final class CameraAspectRatio {
|
||||
private static final float SENSOR = -1;
|
@ -1,8 +1,5 @@
|
||||
package com.genymobile.scrcpy.video;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.util.HandlerExecutor;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.device.Size;
|
||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||
|
||||
import android.annotation.SuppressLint;
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.video;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.hardware.camera2.CameraCharacteristics;
|
||||
@ -21,7 +21,7 @@ public enum CameraFacing {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static CameraFacing findByName(String name) {
|
||||
static CameraFacing findByName(String name) {
|
||||
for (CameraFacing facing : CameraFacing.values()) {
|
||||
if (name.equals(facing.name)) {
|
||||
return facing;
|
@ -1,10 +1,5 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.device.Device;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.util.Settings;
|
||||
import com.genymobile.scrcpy.util.SettingsException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.util;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
public interface Codec {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.util;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
@ -1,7 +1,4 @@
|
||||
package com.genymobile.scrcpy.util;
|
||||
|
||||
import com.genymobile.scrcpy.audio.AudioCodec;
|
||||
import com.genymobile.scrcpy.video.VideoCodec;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecList;
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.util;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.device;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
public class ConfigurationException extends Exception {
|
||||
public ConfigurationException(String message) {
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.control;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.net.LocalSocket;
|
||||
|
@ -1,6 +1,4 @@
|
||||
package com.genymobile.scrcpy.control;
|
||||
|
||||
import com.genymobile.scrcpy.device.Position;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
/**
|
||||
* Union of all supported event types, identified by their {@code type}.
|
@ -1,8 +1,4 @@
|
||||
package com.genymobile.scrcpy.control;
|
||||
|
||||
import com.genymobile.scrcpy.util.Binary;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.device.Position;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
@ -1,11 +1,5 @@
|
||||
package com.genymobile.scrcpy.control;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.AsyncProcessor;
|
||||
import com.genymobile.scrcpy.CleanUp;
|
||||
import com.genymobile.scrcpy.device.Device;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.device.Point;
|
||||
import com.genymobile.scrcpy.device.Position;
|
||||
import com.genymobile.scrcpy.wrappers.InputManager;
|
||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||
|
||||
@ -28,6 +22,7 @@ public class Controller implements AsyncProcessor {
|
||||
|
||||
// control_msg.h values of the pointerId field in inject_touch_event message
|
||||
private static final int POINTER_ID_MOUSE = -1;
|
||||
private static final int POINTER_ID_VIRTUAL_MOUSE = -3;
|
||||
|
||||
private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
@ -278,9 +273,8 @@ public class Controller implements AsyncProcessor {
|
||||
pointer.setPressure(pressure);
|
||||
|
||||
int source;
|
||||
boolean activeSecondaryButtons = ((actionButton | buttons) & ~MotionEvent.BUTTON_PRIMARY) != 0;
|
||||
if (pointerId == POINTER_ID_MOUSE && (action == MotionEvent.ACTION_HOVER_MOVE || activeSecondaryButtons)) {
|
||||
// real mouse event, or event incompatible with a finger
|
||||
if (pointerId == POINTER_ID_MOUSE || pointerId == POINTER_ID_VIRTUAL_MOUSE) {
|
||||
// real mouse event (forced by the client when --forward-on-click)
|
||||
pointerProperties[pointerIndex].toolType = MotionEvent.TOOL_TYPE_MOUSE;
|
||||
source = InputDevice.SOURCE_MOUSE;
|
||||
pointer.setUp(buttons == 0);
|
@ -1,8 +1,4 @@
|
||||
package com.genymobile.scrcpy.device;
|
||||
|
||||
import com.genymobile.scrcpy.control.ControlChannel;
|
||||
import com.genymobile.scrcpy.util.IO;
|
||||
import com.genymobile.scrcpy.util.StringUtils;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.net.LocalServerSocket;
|
||||
import android.net.LocalSocket;
|
@ -1,9 +1,5 @@
|
||||
package com.genymobile.scrcpy.device;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.Options;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.util.LogUtils;
|
||||
import com.genymobile.scrcpy.video.ScreenInfo;
|
||||
import com.genymobile.scrcpy.wrappers.ClipboardManager;
|
||||
import com.genymobile.scrcpy.wrappers.DisplayControl;
|
||||
import com.genymobile.scrcpy.wrappers.InputManager;
|
||||
@ -323,22 +319,10 @@ public final class Device {
|
||||
* @param mode one of the {@code POWER_MODE_*} constants
|
||||
*/
|
||||
public static boolean setScreenPowerMode(int mode) {
|
||||
boolean applyToMultiPhysicalDisplays = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
|
||||
|
||||
if (applyToMultiPhysicalDisplays
|
||||
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
|
||||
&& Build.BRAND.equalsIgnoreCase("honor")
|
||||
&& SurfaceControl.hasGetBuildInDisplayMethod()) {
|
||||
// Workaround for Honor devices with Android 14:
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/4823>
|
||||
// - <https://github.com/Genymobile/scrcpy/issues/4943>
|
||||
applyToMultiPhysicalDisplays = false;
|
||||
}
|
||||
|
||||
if (applyToMultiPhysicalDisplays) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
// On Android 14, these internal methods have been moved to DisplayControl
|
||||
boolean useDisplayControl =
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !SurfaceControl.hasGetPhysicalDisplayIdsMethod();
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !SurfaceControl.hasPhysicalDisplayIdsMethod();
|
||||
|
||||
// Change the power mode for all physical displays
|
||||
long[] physicalDisplayIds = useDisplayControl ? DisplayControl.getPhysicalDisplayIds() : SurfaceControl.getPhysicalDisplayIds();
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.control;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
public final class DeviceMessage {
|
||||
|
@ -1,6 +1,4 @@
|
||||
package com.genymobile.scrcpy.control;
|
||||
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
@ -1,7 +1,4 @@
|
||||
package com.genymobile.scrcpy.control;
|
||||
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.util.StringUtils;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.device;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
public final class DisplayInfo {
|
||||
private final int displayId;
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.util;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.os.Handler;
|
||||
|
@ -1,6 +1,4 @@
|
||||
package com.genymobile.scrcpy.util;
|
||||
|
||||
import com.genymobile.scrcpy.BuildConfig;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.control;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.util;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
@ -19,7 +19,7 @@ public final class Ln {
|
||||
private static final PrintStream CONSOLE_OUT = new PrintStream(new FileOutputStream(FileDescriptor.out));
|
||||
private static final PrintStream CONSOLE_ERR = new PrintStream(new FileOutputStream(FileDescriptor.err));
|
||||
|
||||
public enum Level {
|
||||
enum Level {
|
||||
VERBOSE, DEBUG, INFO, WARN, ERROR
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
package com.genymobile.scrcpy.util;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.device.DisplayInfo;
|
||||
import com.genymobile.scrcpy.device.Size;
|
||||
import com.genymobile.scrcpy.wrappers.DisplayManager;
|
||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||
|
@ -1,15 +1,5 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.audio.AudioCodec;
|
||||
import com.genymobile.scrcpy.audio.AudioSource;
|
||||
import com.genymobile.scrcpy.device.Size;
|
||||
import com.genymobile.scrcpy.util.CodecOption;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.video.CameraAspectRatio;
|
||||
import com.genymobile.scrcpy.video.CameraFacing;
|
||||
import com.genymobile.scrcpy.video.VideoCodec;
|
||||
import com.genymobile.scrcpy.video.VideoSource;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
import java.util.List;
|
||||
@ -26,7 +16,6 @@ public class Options {
|
||||
private AudioCodec audioCodec = AudioCodec.OPUS;
|
||||
private VideoSource videoSource = VideoSource.DISPLAY;
|
||||
private AudioSource audioSource = AudioSource.OUTPUT;
|
||||
private boolean audioDup;
|
||||
private int videoBitRate = 8000000;
|
||||
private int audioBitRate = 128000;
|
||||
private int maxFps;
|
||||
@ -101,10 +90,6 @@ public class Options {
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
public boolean getAudioDup() {
|
||||
return audioDup;
|
||||
}
|
||||
|
||||
public int getVideoBitRate() {
|
||||
return videoBitRate;
|
||||
}
|
||||
@ -308,9 +293,6 @@ public class Options {
|
||||
}
|
||||
options.audioSource = audioSource;
|
||||
break;
|
||||
case "audio_dup":
|
||||
options.audioDup = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "max_size":
|
||||
options.maxSize = Integer.parseInt(value) & ~7; // multiple of 8
|
||||
break;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.device;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import java.util.Objects;
|
||||
|
@ -1,6 +1,4 @@
|
||||
package com.genymobile.scrcpy.control;
|
||||
|
||||
import com.genymobile.scrcpy.device.Point;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
public class Pointer {
|
||||
|
@ -1,6 +1,4 @@
|
||||
package com.genymobile.scrcpy.control;
|
||||
|
||||
import com.genymobile.scrcpy.device.Point;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.device;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import java.util.Objects;
|
||||
|
@ -1,8 +1,5 @@
|
||||
package com.genymobile.scrcpy.video;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.device.Device;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.device.Size;
|
||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||
import com.genymobile.scrcpy.wrappers.SurfaceControl;
|
||||
|
||||
@ -48,18 +45,18 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList
|
||||
}
|
||||
|
||||
try {
|
||||
display = createDisplay();
|
||||
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
|
||||
Ln.d("Display: using SurfaceControl API");
|
||||
} catch (Exception surfaceControlException) {
|
||||
Rect videoRect = screenInfo.getVideoSize().toRect();
|
||||
virtualDisplay = ServiceManager.getDisplayManager()
|
||||
.createVirtualDisplay("scrcpy", videoRect.width(), videoRect.height(), device.getDisplayId(), surface);
|
||||
Ln.d("Display: using DisplayManager API");
|
||||
} catch (Exception displayManagerException) {
|
||||
try {
|
||||
display = createDisplay();
|
||||
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
|
||||
Ln.d("Display: using SurfaceControl API");
|
||||
} catch (Exception surfaceControlException) {
|
||||
Ln.e("Could not create display using DisplayManager", displayManagerException);
|
||||
virtualDisplay = ServiceManager.getDisplayManager()
|
||||
.createVirtualDisplay("scrcpy", videoRect.width(), videoRect.height(), device.getDisplayId(), surface);
|
||||
Ln.d("Display: using DisplayManager API");
|
||||
} catch (Exception displayManagerException) {
|
||||
Ln.e("Could not create display using SurfaceControl", surfaceControlException);
|
||||
Ln.e("Could not create display using DisplayManager", displayManagerException);
|
||||
throw new AssertionError("Could not create display");
|
||||
}
|
||||
}
|
||||
@ -71,11 +68,6 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList
|
||||
device.setFoldListener(null);
|
||||
if (display != null) {
|
||||
SurfaceControl.destroyDisplay(display);
|
||||
display = null;
|
||||
}
|
||||
if (virtualDisplay != null) {
|
||||
virtualDisplay.release();
|
||||
virtualDisplay = null;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,4 @@
|
||||
package com.genymobile.scrcpy.video;
|
||||
|
||||
import com.genymobile.scrcpy.BuildConfig;
|
||||
import com.genymobile.scrcpy.device.Device;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.device.Size;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
@ -1,29 +1,5 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.audio.AudioCapture;
|
||||
import com.genymobile.scrcpy.audio.AudioCodec;
|
||||
import com.genymobile.scrcpy.audio.AudioDirectCapture;
|
||||
import com.genymobile.scrcpy.audio.AudioEncoder;
|
||||
import com.genymobile.scrcpy.audio.AudioPlaybackCapture;
|
||||
import com.genymobile.scrcpy.audio.AudioRawRecorder;
|
||||
import com.genymobile.scrcpy.audio.AudioSource;
|
||||
import com.genymobile.scrcpy.control.ControlChannel;
|
||||
import com.genymobile.scrcpy.control.Controller;
|
||||
import com.genymobile.scrcpy.control.DeviceMessage;
|
||||
import com.genymobile.scrcpy.device.ConfigurationException;
|
||||
import com.genymobile.scrcpy.device.DesktopConnection;
|
||||
import com.genymobile.scrcpy.device.Device;
|
||||
import com.genymobile.scrcpy.device.Streamer;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.util.LogUtils;
|
||||
import com.genymobile.scrcpy.util.Settings;
|
||||
import com.genymobile.scrcpy.util.SettingsException;
|
||||
import com.genymobile.scrcpy.video.CameraCapture;
|
||||
import com.genymobile.scrcpy.video.ScreenCapture;
|
||||
import com.genymobile.scrcpy.video.SurfaceCapture;
|
||||
import com.genymobile.scrcpy.video.SurfaceEncoder;
|
||||
import com.genymobile.scrcpy.video.VideoSource;
|
||||
|
||||
import android.os.BatteryManager;
|
||||
import android.os.Build;
|
||||
|
||||
@ -144,7 +120,7 @@ public final class Server {
|
||||
|
||||
final Device device = camera ? null : new Device(options);
|
||||
|
||||
Workarounds.apply();
|
||||
Workarounds.apply(audio, camera);
|
||||
|
||||
List<AsyncProcessor> asyncProcessors = new ArrayList<>();
|
||||
|
||||
@ -166,14 +142,7 @@ public final class Server {
|
||||
|
||||
if (audio) {
|
||||
AudioCodec audioCodec = options.getAudioCodec();
|
||||
AudioSource audioSource = options.getAudioSource();
|
||||
AudioCapture audioCapture;
|
||||
if (audioSource.isDirect()) {
|
||||
audioCapture = new AudioDirectCapture(audioSource);
|
||||
} else {
|
||||
audioCapture = new AudioPlaybackCapture(options.getAudioDup());
|
||||
}
|
||||
|
||||
AudioCapture audioCapture = new AudioCapture(options.getAudioSource());
|
||||
Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(), options.getSendFrameMeta());
|
||||
AsyncProcessor audioRecorder;
|
||||
if (audioCodec == AudioCodec.RAW) {
|
||||
@ -279,7 +248,7 @@ public final class Server {
|
||||
Ln.i(LogUtils.buildDisplayListMessage());
|
||||
}
|
||||
if (options.getListCameras() || options.getListCameraSizes()) {
|
||||
Workarounds.apply();
|
||||
Workarounds.apply(false, true);
|
||||
Ln.i(LogUtils.buildCameraListMessage(options.getListCameraSizes()));
|
||||
}
|
||||
// Just print the requested data, do not mirror
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.util;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.wrappers.ContentProvider;
|
||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.util;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
public class SettingsException extends Exception {
|
||||
private static String createMessage(String method, String table, String key, String value) {
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.device;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
@ -1,8 +1,4 @@
|
||||
package com.genymobile.scrcpy.device;
|
||||
|
||||
import com.genymobile.scrcpy.audio.AudioCodec;
|
||||
import com.genymobile.scrcpy.util.Codec;
|
||||
import com.genymobile.scrcpy.util.IO;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.util;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
public final class StringUtils {
|
||||
private StringUtils() {
|
@ -1,6 +1,4 @@
|
||||
package com.genymobile.scrcpy.video;
|
||||
|
||||
import com.genymobile.scrcpy.device.Size;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.view.Surface;
|
||||
|
@ -1,15 +1,4 @@
|
||||
package com.genymobile.scrcpy.video;
|
||||
|
||||
import com.genymobile.scrcpy.AsyncProcessor;
|
||||
import com.genymobile.scrcpy.util.Codec;
|
||||
import com.genymobile.scrcpy.util.CodecOption;
|
||||
import com.genymobile.scrcpy.util.CodecUtils;
|
||||
import com.genymobile.scrcpy.device.ConfigurationException;
|
||||
import com.genymobile.scrcpy.util.IO;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.util.LogUtils;
|
||||
import com.genymobile.scrcpy.device.Size;
|
||||
import com.genymobile.scrcpy.device.Streamer;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
@ -1,6 +1,4 @@
|
||||
package com.genymobile.scrcpy.control;
|
||||
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.HandlerThread;
|
@ -1,6 +1,4 @@
|
||||
package com.genymobile.scrcpy.video;
|
||||
|
||||
import com.genymobile.scrcpy.util.Codec;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.media.MediaFormat;
|
@ -1,4 +1,4 @@
|
||||
package com.genymobile.scrcpy.video;
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
public enum VideoSource {
|
||||
DISPLAY("display"),
|
||||
@ -10,7 +10,7 @@ public enum VideoSource {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static VideoSource findByName(String name) {
|
||||
static VideoSource findByName(String name) {
|
||||
for (VideoSource videoSource : VideoSource.values()) {
|
||||
if (name.equals(videoSource.name)) {
|
||||
return videoSource;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user