Merge branch 'Genymobile:master' into master

This commit is contained in:
HackSys Team 2024-07-15 15:16:20 +05:30 committed by GitHub
commit baaf08092c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 1171 additions and 493 deletions

View file

@ -7,17 +7,25 @@ assignees: ''
--- ---
- [ ] I have read the [FAQ](https://github.com/Genymobile/scrcpy/blob/master/FAQ.md). _Please read the [prerequisites] to run scrcpy._
- [ ] I have searched in existing [issues](https://github.com/Genymobile/scrcpy/issues).
**Environment** [prerequisites]: https://github.com/Genymobile/scrcpy#prerequisites
- OS: [e.g. Debian, Windows, macOS...]
- scrcpy version: [e.g. 1.12.1] _Also read the [FAQ] and check if your [issue][issues] already exists._
- installation method: [e.g. manual build, apt, snap, brew, Windows release...]
- device model: [FAQ]: https://github.com/Genymobile/scrcpy/blob/master/FAQ.md
- Android version: [e.g. 10] [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
**Describe the bug**
A clear and concise description of what the bug is. A clear and concise description of what the bug is.
On errors, please provide the output of the console (and `adb logcat` if relevant). On errors, please provide the output of the console (and `adb logcat` if relevant).

View file

@ -2,7 +2,7 @@
source for the project. Do not download releases from random websites, even if source for the project. Do not download releases from random websites, even if
their name contains `scrcpy`.** their name contains `scrcpy`.**
# scrcpy (v2.4) # scrcpy (v2.5)
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" /> <img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />

View file

@ -25,7 +25,6 @@ _scrcpy() {
-e --select-tcpip -e --select-tcpip
-f --fullscreen -f --fullscreen
--force-adb-forward --force-adb-forward
--forward-all-clicks
-h --help -h --help
-K -K
--keyboard= --keyboard=
@ -41,6 +40,7 @@ _scrcpy() {
-M -M
--max-fps= --max-fps=
--mouse= --mouse=
--mouse-bind=
-n --no-control -n --no-control
-N --no-playback -N --no-playback
--no-audio --no-audio
@ -50,6 +50,7 @@ _scrcpy() {
--no-downsize-on-error --no-downsize-on-error
--no-key-repeat --no-key-repeat
--no-mipmaps --no-mipmaps
--no-mouse-hover
--no-power-on --no-power-on
--no-video --no-video
--no-video-playback --no-video-playback

View file

@ -32,10 +32,9 @@ arguments=(
{-e,--select-tcpip}'[Use TCP/IP device]' {-e,--select-tcpip}'[Use TCP/IP device]'
{-f,--fullscreen}'[Start in fullscreen]' {-f,--fullscreen}'[Start in fullscreen]'
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]' '--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]' {-h,--help}'[Print the help]'
'-K[Use UHID keyboard (same as --keyboard=uhid)]' '-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]' '--kill-adb-on-close[Kill adb when scrcpy terminates]'
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
'--list-camera-sizes[List the valid camera capture sizes]' '--list-camera-sizes[List the valid camera capture sizes]'
@ -46,7 +45,8 @@ arguments=(
{-m,--max-size=}'[Limit both the width and height of the video to value]' {-m,--max-size=}'[Limit both the width and height of the video to value]'
'-M[Use UHID mouse (same as --mouse=uhid)]' '-M[Use UHID mouse (same as --mouse=uhid)]'
'--max-fps=[Limit the frame rate of screen capture]' '--max-fps=[Limit the frame rate of screen capture]'
'--mouse[Set the mouse input mode]:mode:(disabled sdk uhid aoa)' '--mouse=[Set the mouse input mode]:mode:(disabled sdk uhid aoa)'
'--mouse-bind=[Configure bindings of secondary clicks]'
{-n,--no-control}'[Disable device control \(mirror the device in read only\)]' {-n,--no-control}'[Disable device control \(mirror the device in read only\)]'
{-N,--no-playback}'[Disable video and audio playback]' {-N,--no-playback}'[Disable video and audio playback]'
'--no-audio[Disable audio forwarding]' '--no-audio[Disable audio forwarding]'
@ -56,6 +56,7 @@ arguments=(
'--no-downsize-on-error[Disable lowering definition on MediaCodec error]' '--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-key-repeat[Do not forward repeated key events when a key is held down]'
'--no-mipmaps[Disable the generation of mipmaps]' '--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-power-on[Do not power on the device on start]'
'--no-video[Disable video forwarding]' '--no-video[Disable video forwarding]'
'--no-video-playback[Disable video playback]' '--no-video-playback[Disable video playback]'

View file

@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR" cd "$DEPS_DIR"
. common . common
VERSION=34.0.5 VERSION=35.0.0
FILENAME=platform-tools_r$VERSION-windows.zip FILENAME=platform-tools_r$VERSION-windows.zip
PROJECT_DIR=platform-tools-$VERSION PROJECT_DIR=platform-tools-$VERSION
SHA256SUM=3f8320152704377de150418a3c4c9d07d16d80a6c0d0d8f7289c22c499e33571 SHA256SUM=7ab78a8f8b305ae4d0de647d99c43599744de61a0838d3a47bda0cdffefee87e
cd "$SOURCES_DIR" cd "$SOURCES_DIR"

View file

@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR" cd "$DEPS_DIR"
. common . common
VERSION=6.1.1 VERSION=7.0.1
FILENAME=ffmpeg-$VERSION.tar.xz FILENAME=ffmpeg-$VERSION.tar.xz
PROJECT_DIR=ffmpeg-$VERSION PROJECT_DIR=ffmpeg-$VERSION
SHA256SUM=8684f4b00f94b85461884c3719382f1261f0d9eb3d59640a1f4ac0873616f968 SHA256SUM=bce9eeb0f17ef8982390b1f37711a61b4290dc8c2a0c1a37b5857e85bfb0e4ff
cd "$SOURCES_DIR" cd "$SOURCES_DIR"
@ -17,7 +17,6 @@ then
else else
get_file "https://ffmpeg.org/releases/$FILENAME" "$FILENAME" "$SHA256SUM" get_file "https://ffmpeg.org/releases/$FILENAME" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR" tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
patch -d "$PROJECT_DIR" -p1 < "$PATCHES_DIR"/ffmpeg-6.1-fix-build.patch
fi fi
mkdir -p "$BUILD_DIR/$PROJECT_DIR" mkdir -p "$BUILD_DIR/$PROJECT_DIR"

View file

@ -5,9 +5,9 @@ cd "$DEPS_DIR"
. common . common
VERSION=1.0.27 VERSION=1.0.27
FILENAME=libusb-$VERSION.tar.bz2 FILENAME=libusb-$VERSION.tar.gz
PROJECT_DIR=libusb-$VERSION PROJECT_DIR=libusb-$VERSION
SHA256SUM=ffaa41d741a8a3bee244ac8e54a72ea05bf2879663c098c82fc5757853441575 SHA256SUM=e8f18a7a36ecbb11fb820bd71540350d8f61bcd9db0d2e8c18a6fb80b214a3de
cd "$SOURCES_DIR" cd "$SOURCES_DIR"
@ -15,7 +15,7 @@ if [[ -d "$PROJECT_DIR" ]]
then then
echo "$PWD/$PROJECT_DIR" found echo "$PWD/$PROJECT_DIR" found
else else
get_file "https://github.com/libusb/libusb/releases/download/v$VERSION/libusb-$VERSION.tar.bz2" "$FILENAME" "$SHA256SUM" get_file "https://github.com/libusb/libusb/archive/refs/tags/v$VERSION.tar.gz" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR" tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
fi fi
@ -33,6 +33,7 @@ else
mkdir "$HOST" mkdir "$HOST"
cd "$HOST" cd "$HOST"
"$SOURCES_DIR/$PROJECT_DIR"/bootstrap.sh
"$SOURCES_DIR/$PROJECT_DIR"/configure \ "$SOURCES_DIR/$PROJECT_DIR"/configure \
--prefix="$INSTALL_DIR/$HOST" \ --prefix="$INSTALL_DIR/$HOST" \
--host="$HOST_TRIPLET" \ --host="$HOST_TRIPLET" \

View file

@ -1,27 +0,0 @@
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

View file

@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR" cd "$DEPS_DIR"
. common . common
VERSION=2.28.5 VERSION=2.30.4
FILENAME=SDL-$VERSION.tar.gz FILENAME=SDL-$VERSION.tar.gz
PROJECT_DIR=SDL-release-$VERSION PROJECT_DIR=SDL-release-$VERSION
SHA256SUM=9f0556e4a24ef5b267010038ad9e9948b62f236d5bcc4b22179f95ef62d84023 SHA256SUM=dcc2c8c9c3e9e1a7c8d61d9522f1cba4e9b740feb560dcb15234030984610ee2
cd "$SOURCES_DIR" cd "$SOURCES_DIR"

View file

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

View file

@ -163,10 +163,6 @@ Start in fullscreen.
.B \-\-force\-adb\-forward .B \-\-force\-adb\-forward
Do not attempt to use "adb reverse" to connect to the device. 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 .TP
.B \-h, \-\-help .B \-h, \-\-help
Print this help. Print this help.
@ -261,6 +257,23 @@ LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse bac
Also see \fB\-\-keyboard\fR. Also see \fB\-\-keyboard\fR.
.TP
.BI "\-\-mouse\-bind " xxxx
Configure bindings of secondary clicks.
The argument must be exactly 4 characters, one for each secondary click (in order: right click, middle click, 4th click, 5th 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"
Default is 'bhsn' for SDK mouse, and '++++' for AOA and UHID.
.TP .TP
.B \-n, \-\-no\-control .B \-n, \-\-no\-control
@ -304,6 +317,10 @@ Do not forward repeated key events when a key is held down.
.B \-\-no\-mipmaps .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. 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 .TP
.B \-\-no\-power\-on .B \-\-no\-power\-on
Do not power on the device on start. Do not power on the device on start.
@ -316,6 +333,10 @@ Disable video forwarding.
.B \-\-no\-video\-playback .B \-\-no\-video\-playback
Disable video playback on the computer. Disable video playback on the computer.
.TP
.B \-\-no\-window
Disable scrcpy window. Implies --no-video-playback and --no-control.
.TP .TP
.BI "\-\-orientation " value .BI "\-\-orientation " value
Same as --display-orientation=value --record-orientation=value. Same as --display-orientation=value --record-orientation=value.
@ -420,9 +441,9 @@ Turn the device screen off immediately.
.BI "\-\-shortcut\-mod " key\fR[+...]][,...] .BI "\-\-shortcut\-mod " key\fR[+...]][,...]
Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper". Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper".
A shortcut can consist in several keys, separated by '+'. Several shortcuts can be specified, separated by ','. Several shortcut modifiers can be specified, separated by ','.
For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctrl+lalt,lsuper". For example, to use either LCtrl or LSuper for scrcpy shortcuts, pass "lctrl,lsuper".
Default is "lalt,lsuper" (left-Alt or left-Super). Default is "lalt,lsuper" (left-Alt or left-Super).
@ -577,6 +598,14 @@ Flip display horizontally
.B MOD+Shift+Up, MOD+Shift+Down .B MOD+Shift+Up, MOD+Shift+Down
Flip display vertically Flip display vertically
.TP
.B MOD+z
Pause or re-pause display
.TP
.B MOD+Shift+z
Unpause display
.TP .TP
.B MOD+g .B MOD+g
Resize window to 1:1 (pixel\-perfect) Resize window to 1:1 (pixel\-perfect)

View file

@ -194,7 +194,11 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
// Still insufficient, drop old samples to make space // Still insufficient, drop old samples to make space
skipped_samples = sc_audiobuf_read(&ap->buf, NULL, remaining); skipped_samples = sc_audiobuf_read(&ap->buf, NULL, remaining);
assert(skipped_samples == remaining); assert(skipped_samples == remaining);
}
SDL_UnlockAudioDevice(ap->device);
if (written < samples) {
// Now there is enough space // Now there is enough space
uint32_t w = sc_audiobuf_write(&ap->buf, uint32_t w = sc_audiobuf_write(&ap->buf,
swr_buf + TO_BYTES(written), swr_buf + TO_BYTES(written),
@ -202,8 +206,6 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
assert(w == remaining); assert(w == remaining);
(void) w; (void) w;
} }
SDL_UnlockAudioDevice(ap->device);
} }
uint32_t underflow = 0; uint32_t underflow = 0;

View file

@ -97,6 +97,9 @@ enum {
OPT_MOUSE, OPT_MOUSE,
OPT_HID_KEYBOARD_DEPRECATED, OPT_HID_KEYBOARD_DEPRECATED,
OPT_HID_MOUSE_DEPRECATED, OPT_HID_MOUSE_DEPRECATED,
OPT_NO_WINDOW,
OPT_MOUSE_BIND,
OPT_NO_MOUSE_HOVER,
}; };
struct sc_option { struct sc_option {
@ -351,11 +354,9 @@ static const struct sc_option options[] = {
"device.", "device.",
}, },
{ {
// deprecated
.longopt_id = OPT_FORWARD_ALL_CLICKS, .longopt_id = OPT_FORWARD_ALL_CLICKS,
.longopt = "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', .shortopt = 'h',
@ -489,6 +490,23 @@ static const struct sc_option options[] = {
"control of the mouse back to the computer.\n" "control of the mouse back to the computer.\n"
"Also see --keyboard.", "Also see --keyboard.",
}, },
{
.longopt_id = OPT_MOUSE_BIND,
.longopt = "mouse-bind",
.argdesc = "xxxx",
.text = "Configure bindings of secondary clicks.\n"
"The argument must be exactly 4 characters, one for each "
"secondary click (in order: right click, middle click, 4th "
"click, 5th click).\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 '++++' for AOA and UHID.",
},
{ {
.shortopt = 'n', .shortopt = 'n',
.longopt = "no-control", .longopt = "no-control",
@ -551,6 +569,12 @@ static const struct sc_option options[] = {
"mipmaps are automatically generated to improve downscaling " "mipmaps are automatically generated to improve downscaling "
"quality. This option disables the generation of mipmaps.", "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_id = OPT_NO_POWER_ON,
.longopt = "no-power-on", .longopt = "no-power-on",
@ -566,6 +590,12 @@ static const struct sc_option options[] = {
.longopt = "no-video-playback", .longopt = "no-video-playback",
.text = "Disable video playback on the computer.", .text = "Disable video playback on the computer.",
}, },
{
.longopt_id = OPT_NO_WINDOW,
.longopt = "no-window",
.text = "Disable scrcpy window. Implies --no-video-playback and "
"--no-control.",
},
{ {
.longopt_id = OPT_ORIENTATION, .longopt_id = OPT_ORIENTATION,
.longopt = "orientation", .longopt = "orientation",
@ -709,10 +739,10 @@ static const struct sc_option options[] = {
.text = "Specify the modifiers to use for scrcpy shortcuts.\n" .text = "Specify the modifiers to use for scrcpy shortcuts.\n"
"Possible keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\", " "Possible keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\", "
"\"lsuper\" and \"rsuper\".\n" "\"lsuper\" and \"rsuper\".\n"
"A shortcut can consist in several keys, separated by '+'. " "Several shortcut modifiers can be specified, separated by "
"Several shortcuts can be specified, separated by ','.\n" "','.\n"
"For example, to use either LCtrl+LAlt or LSuper for scrcpy " "For example, to use either LCtrl or LSuper for scrcpy "
"shortcuts, pass \"lctrl+lalt,lsuper\".\n" "shortcuts, pass \"lctrl,lsuper\".\n"
"Default is \"lalt,lsuper\" (left-Alt or left-Super).", "Default is \"lalt,lsuper\" (left-Alt or left-Super).",
}, },
{ {
@ -900,6 +930,14 @@ static const struct sc_shortcut shortcuts[] = {
.shortcuts = { "MOD+Shift+Up", "MOD+Shift+Down" }, .shortcuts = { "MOD+Shift+Up", "MOD+Shift+Down" },
.text = "Flip display vertically", .text = "Flip display vertically",
}, },
{
.shortcuts = { "MOD+z" },
.text = "Pause or re-pause display",
},
{
.shortcuts = { "MOD+Shift+z" },
.text = "Unpause display",
},
{ {
.shortcuts = { "MOD+g" }, .shortcuts = { "MOD+g" },
.text = "Resize window to 1:1 (pixel-perfect)", .text = "Resize window to 1:1 (pixel-perfect)",
@ -1672,82 +1710,62 @@ parse_log_level(const char *s, enum sc_log_level *log_level) {
return false; return false;
} }
// item is a list of mod keys separated by '+' (e.g. "lctrl+lalt") static enum sc_shortcut_mod
// 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) { 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) \ #define STREQ(literal, s, len) \
((sizeof(literal)-1 == len) && !memcmp(literal, s, len)) ((sizeof(literal)-1 == len) && !memcmp(literal, s, len))
if (STREQ("lctrl", item, key_len)) { if (STREQ("lctrl", item, len)) {
mod |= SC_SHORTCUT_MOD_LCTRL; return SC_SHORTCUT_MOD_LCTRL;
} else if (STREQ("rctrl", item, key_len)) { }
mod |= SC_SHORTCUT_MOD_RCTRL; if (STREQ("rctrl", item, len)) {
} else if (STREQ("lalt", item, key_len)) { return SC_SHORTCUT_MOD_RCTRL;
mod |= SC_SHORTCUT_MOD_LALT; }
} else if (STREQ("ralt", item, key_len)) { if (STREQ("lalt", item, len)) {
mod |= SC_SHORTCUT_MOD_RALT; return SC_SHORTCUT_MOD_LALT;
} else if (STREQ("lsuper", item, key_len)) { }
mod |= SC_SHORTCUT_MOD_LSUPER; if (STREQ("ralt", item, len)) {
} else if (STREQ("rsuper", item, key_len)) { return SC_SHORTCUT_MOD_RALT;
mod |= SC_SHORTCUT_MOD_RSUPER; }
} else { if (STREQ("lsuper", item, len)) {
LOGE("Unknown modifier key: %.*s " return SC_SHORTCUT_MOD_LSUPER;
"(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)", }
(int) key_len, item); if (STREQ("rsuper", item, len)) {
return 0; return SC_SHORTCUT_MOD_RSUPER;
} }
#undef STREQ #undef STREQ
if (!has_plus) { bool has_plus = strchr(item, '+');
break; if (has_plus) {
} LOGE("Shortcut mod combination with '+' is not supported anymore: "
"'%.*s' (see #4741)", (int) len, item);
item = plus + 1; return 0;
assert(len >= key_len + 1);
len -= key_len + 1;
} }
return mod; LOGE("Unknown modifier key: %.*s "
"(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)",
(int) len, item);
return 0;
} }
static bool static bool
parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) { parse_shortcut_mods(const char *s, uint8_t *shortcut_mods) {
unsigned count = 0; uint8_t mods = 0;
unsigned current = 0;
// LCtrl+LAlt or RCtrl or LCtrl+RSuper: "lctrl+lalt,rctrl,lctrl+rsuper" // A list of shortcut modifiers, for example "lctrl,rctrl,rsuper"
for (;;) { for (;;) {
char *comma = strchr(s, ','); char *comma = strchr(s, ',');
if (comma && count == SC_MAX_SHORTCUT_MODS - 1) {
assert(count < SC_MAX_SHORTCUT_MODS);
LOGW("Too many shortcut modifiers alternatives");
return false;
}
assert(!comma || comma > s); assert(!comma || comma > s);
size_t limit = comma ? (size_t) (comma - s) : strlen(s); size_t limit = comma ? (size_t) (comma - s) : strlen(s);
unsigned mod = parse_shortcut_mods_item(s, limit); enum sc_shortcut_mod mod = parse_shortcut_mods_item(s, limit);
if (!mod) { if (!mod) {
LOGE("Invalid modifier keys: %.*s", (int) limit, s);
return false; return false;
} }
mods->data[current++] = mod; mods |= mod;
++count;
if (!comma) { if (!comma) {
break; break;
@ -1756,7 +1774,7 @@ parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
s = comma + 1; s = comma + 1;
} }
mods->count = count; *shortcut_mods = mods;
return true; return true;
} }
@ -1764,7 +1782,7 @@ parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) {
#ifdef SC_TEST #ifdef SC_TEST
// expose the function to unit-tests // expose the function to unit-tests
bool bool
sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) { sc_parse_shortcut_mods(const char *s, uint8_t *mods) {
return parse_shortcut_mods(s, mods); return parse_shortcut_mods(s, mods);
} }
#endif #endif
@ -2043,11 +2061,63 @@ parse_pause_on_exit(const char *s, enum sc_pause_on_exit *pause_on_exit) {
} }
LOGE("Unsupported pause on exit mode: %s " LOGE("Unsupported pause on exit mode: %s "
"(expected true, false or if-error)", optarg); "(expected true, false or if-error)", s);
return false; 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_bindings(const char *s, struct sc_mouse_bindings *mb) {
if (strlen(s) != 4) {
LOGE("Invalid mouse bindings: '%s' (expected exactly 4 characters from "
"{'+', '-', 'b', 'h', 's', 'n'})", s);
return false;
}
if (!parse_mouse_binding(s[0], &mb->right_click)) {
return false;
}
if (!parse_mouse_binding(s[1], &mb->middle_click)) {
return false;
}
if (!parse_mouse_binding(s[2], &mb->click4)) {
return false;
}
if (!parse_mouse_binding(s[3], &mb->click5)) {
return false;
}
return true;
}
static bool static bool
parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
const char *optstring, const struct option *longopts) { const char *optstring, const struct option *longopts) {
@ -2130,6 +2200,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false; return false;
} }
break; 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: case OPT_HID_MOUSE_DEPRECATED:
LOGE("--hid-mouse has been removed, use --mouse=aoa or " LOGE("--hid-mouse has been removed, use --mouse=aoa or "
"--mouse=uhid instead."); "--mouse=uhid instead.");
@ -2327,7 +2405,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
} }
break; break;
case OPT_FORWARD_ALL_CLICKS: case OPT_FORWARD_ALL_CLICKS:
opts->forward_all_clicks = true; LOGW("--forward-all-clicks is deprecated, "
"use --mouse-bind=++++ instead.");
opts->mouse_bindings = (struct sc_mouse_bindings) {
.right_click = SC_MOUSE_BINDING_CLICK,
.middle_click = SC_MOUSE_BINDING_CLICK,
.click4 = SC_MOUSE_BINDING_CLICK,
.click5 = SC_MOUSE_BINDING_CLICK,
};
break; break;
case OPT_LEGACY_PASTE: case OPT_LEGACY_PASTE:
opts->legacy_paste = true; opts->legacy_paste = true;
@ -2478,6 +2563,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_CAMERA_HIGH_SPEED: case OPT_CAMERA_HIGH_SPEED:
opts->camera_high_speed = true; opts->camera_high_speed = true;
break; break;
case OPT_NO_WINDOW:
opts->window = false;
break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
return false; return false;
@ -2515,6 +2603,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
v4l2 = !!opts->v4l2_device; v4l2 = !!opts->v4l2_device;
#endif #endif
if (!opts->window) {
// Without window, there cannot be any video playback or control
opts->video_playback = false;
opts->control = false;
}
if (!opts->video) { if (!opts->video) {
opts->video_playback = false; opts->video_playback = false;
// Do not power on the device on start if video capture is disabled // Do not power on the device on start if video capture is disabled
@ -2536,8 +2630,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->audio = false; opts->audio = false;
} }
if (!opts->video && !opts->audio && !otg) { if (!opts->video && !opts->audio && !opts->control && !otg) {
LOGE("No video, no audio, no OTG: nothing to do"); LOGE("No video, no audio, no control, no OTG: nothing to do");
return false; return false;
} }
@ -2548,9 +2642,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
if (opts->audio_playback && opts->audio_buffer == -1) { if (opts->audio_playback && opts->audio_buffer == -1) {
if (opts->audio_codec == SC_CODEC_FLAC) { if (opts->audio_codec == SC_CODEC_FLAC) {
// Use 50 ms audio buffer by default, but use a higher value for FLAC, // Use 50 ms audio buffer by default, but use a higher value for
// which is not low latency (the default encoder produces blocks of // FLAC, which is not low latency (the default encoder produces
// 4096 samples, which represent ~85.333ms). // blocks of 4096 samples, which represent ~85.333ms).
LOGI("FLAC audio: audio buffer increased to 120 ms (use " LOGI("FLAC audio: audio buffer increased to 120 ms (use "
"--audio-buffer to set a custom value)"); "--audio-buffer to set a custom value)");
opts->audio_buffer = SC_TICK_FROM_MS(120); opts->audio_buffer = SC_TICK_FROM_MS(120);
@ -2561,6 +2655,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
if (v4l2) { if (v4l2) {
if (!opts->video) {
LOGE("V4L2 sink requires video capture, but --no-video was set.");
return false;
}
if (opts->lock_video_orientation == if (opts->lock_video_orientation ==
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) { SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
LOGI("Video orientation is locked for v4l2 sink. " LOGI("Video orientation is locked for v4l2 sink. "
@ -2580,16 +2679,57 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
} }
#endif #endif
if (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) { if (opts->control) {
opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA if (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) {
: SC_KEYBOARD_INPUT_MODE_SDK; opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA
: SC_KEYBOARD_INPUT_MODE_SDK;
}
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) {
if (otg) {
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA;
} else if (!opts->video_playback) {
LOGI("No video mirroring, mouse mode switched to UHID");
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_UHID;
} else {
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_SDK;
}
} else if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK
&& !opts->video_playback) {
LOGE("SDK mouse mode requires video playback. Try --mouse=uhid.");
return false;
}
} }
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) {
opts->mouse_input_mode = otg ? SC_MOUSE_INPUT_MODE_AOA // If mouse bindings are not explictly set, configure default bindings
: SC_MOUSE_INPUT_MODE_SDK; if (opts->mouse_bindings.right_click == SC_MOUSE_BINDING_AUTO) {
assert(opts->mouse_bindings.middle_click == SC_MOUSE_BINDING_AUTO);
assert(opts->mouse_bindings.click4 == SC_MOUSE_BINDING_AUTO);
assert(opts->mouse_bindings.click5 == SC_MOUSE_BINDING_AUTO);
// By default, forward all clicks only for UHID and AOA
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
opts->mouse_bindings = (struct sc_mouse_bindings) {
.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,
};
} else {
opts->mouse_bindings = (struct sc_mouse_bindings) {
.right_click = SC_MOUSE_BINDING_CLICK,
.middle_click = SC_MOUSE_BINDING_CLICK,
.click4 = SC_MOUSE_BINDING_CLICK,
.click5 = SC_MOUSE_BINDING_CLICK,
};
}
} }
if (otg) { if (otg) {
if (!opts->control) {
LOGE("--no-control is not allowed in OTG mode");
return false;
}
enum sc_keyboard_input_mode kmode = opts->keyboard_input_mode; enum sc_keyboard_input_mode kmode = opts->keyboard_input_mode;
if (kmode != SC_KEYBOARD_INPUT_MODE_AOA if (kmode != SC_KEYBOARD_INPUT_MODE_AOA
&& kmode != SC_KEYBOARD_INPUT_MODE_DISABLED) { && kmode != SC_KEYBOARD_INPUT_MODE_DISABLED) {
@ -2628,6 +2768,12 @@ 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) { if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) {
LOGI("Tunnel host/port is set, " LOGI("Tunnel host/port is set, "
"--force-adb-forward automatically enabled."); "--force-adb-forward automatically enabled.");
@ -2692,6 +2838,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
} }
if (opts->record_filename) { if (opts->record_filename) {
if (!opts->video && !opts->audio) {
LOGE("Video and audio disabled, nothing to record");
return false;
}
if (!opts->record_format) { if (!opts->record_format) {
opts->record_format = guess_record_format(opts->record_filename); opts->record_format = guess_record_format(opts->record_filename);
if (!opts->record_format) { if (!opts->record_format) {
@ -2798,6 +2949,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
} }
# endif # endif
if (opts->start_fps_counter && !opts->video_playback) {
LOGW("--print-fps has no effect without video playback");
opts->start_fps_counter = false;
}
if (otg) { if (otg) {
// OTG mode is compatible with only very few options. // OTG mode is compatible with only very few options.
// Only report obvious errors. // Only report obvious errors.

View file

@ -28,7 +28,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]);
#ifdef SC_TEST #ifdef SC_TEST
bool bool
sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods); sc_parse_shortcut_mods(const char *s, uint8_t *shortcut_mods);
#endif #endif
#endif #endif

View file

@ -6,8 +6,19 @@
#define SC_CONTROL_MSG_QUEUE_MAX 64 #define SC_CONTROL_MSG_QUEUE_MAX 64
static void
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_error(controller, controller->cbs_userdata);
}
bool bool
sc_controller_init(struct sc_controller *controller, sc_socket control_socket) { sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
const struct sc_controller_callbacks *cbs,
void *cbs_userdata) {
sc_vecdeque_init(&controller->queue); sc_vecdeque_init(&controller->queue);
bool ok = sc_vecdeque_reserve(&controller->queue, SC_CONTROL_MSG_QUEUE_MAX); bool ok = sc_vecdeque_reserve(&controller->queue, SC_CONTROL_MSG_QUEUE_MAX);
@ -15,7 +26,12 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket) {
return false; return false;
} }
ok = sc_receiver_init(&controller->receiver, control_socket); static const struct sc_receiver_callbacks receiver_cbs = {
.on_error = sc_controller_receiver_on_error,
};
ok = sc_receiver_init(&controller->receiver, control_socket, &receiver_cbs,
controller);
if (!ok) { if (!ok) {
sc_vecdeque_destroy(&controller->queue); sc_vecdeque_destroy(&controller->queue);
return false; return false;
@ -39,6 +55,10 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket) {
controller->control_socket = control_socket; controller->control_socket = control_socket;
controller->stopped = false; controller->stopped = false;
assert(cbs && cbs->on_error);
controller->cbs = cbs;
controller->cbs_userdata = cbs_userdata;
return true; return true;
} }
@ -125,10 +145,16 @@ run_controller(void *data) {
sc_control_msg_destroy(&msg); sc_control_msg_destroy(&msg);
if (!ok) { if (!ok) {
LOGD("Could not write msg to socket"); LOGD("Could not write msg to socket");
break; goto error;
} }
} }
return 0; return 0;
error:
controller->cbs->on_error(controller, controller->cbs_userdata);
return 1; // ignored
} }
bool bool

View file

@ -22,10 +22,19 @@ struct sc_controller {
bool stopped; bool stopped;
struct sc_control_msg_queue queue; struct sc_control_msg_queue queue;
struct sc_receiver receiver; struct sc_receiver receiver;
const struct sc_controller_callbacks *cbs;
void *cbs_userdata;
};
struct sc_controller_callbacks {
void (*on_error)(struct sc_controller *controller, void *userdata);
}; };
bool bool
sc_controller_init(struct sc_controller *controller, sc_socket control_socket); sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
const struct sc_controller_callbacks *cbs,
void *cbs_userdata);
void void
sc_controller_configure(struct sc_controller *controller, sc_controller_configure(struct sc_controller *controller,

View file

@ -278,7 +278,6 @@ run_demuxer(void *data) {
finally_close_sinks: finally_close_sinks:
sc_packet_source_sinks_close(&demuxer->packet_source); sc_packet_source_sinks_close(&demuxer->packet_source);
finally_free_context: finally_free_context:
// This also calls avcodec_close() internally
avcodec_free_context(&codec_ctx); avcodec_free_context(&codec_ctx);
end: end:
demuxer->cbs->on_ended(demuxer, status, demuxer->cbs_userdata); demuxer->cbs->on_ended(demuxer, status, demuxer->cbs_userdata);

View file

@ -1,11 +1,34 @@
#include "display.h" #include "display.h"
#include <assert.h> #include <assert.h>
#include <libavutil/pixfmt.h>
#include "util/log.h" #include "util/log.h"
static bool
sc_display_init_novideo_icon(struct sc_display *display,
SDL_Surface *icon_novideo) {
assert(icon_novideo);
if (SDL_RenderSetLogicalSize(display->renderer,
icon_novideo->w, icon_novideo->h)) {
LOGW("Could not set renderer logical size: %s", SDL_GetError());
// don't fail
}
display->texture = SDL_CreateTextureFromSurface(display->renderer,
icon_novideo);
if (!display->texture) {
LOGE("Could not create texture: %s", SDL_GetError());
return false;
}
return true;
}
bool bool
sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) { sc_display_init(struct sc_display *display, SDL_Window *window,
SDL_Surface *icon_novideo, bool mipmaps) {
display->renderer = display->renderer =
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!display->renderer) { if (!display->renderer) {
@ -65,6 +88,19 @@ sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) {
display->texture = NULL; display->texture = NULL;
display->pending.flags = 0; display->pending.flags = 0;
display->pending.frame = NULL; display->pending.frame = NULL;
display->has_frame = false;
if (icon_novideo) {
// Without video, set a static scrcpy icon as window content
bool ok = sc_display_init_novideo_icon(display, icon_novideo);
if (!ok) {
#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE
SDL_GL_DeleteContext(display->gl_context);
#endif
SDL_DestroyRenderer(display->renderer);
return false;
}
}
return true; return true;
} }
@ -196,9 +232,25 @@ sc_display_set_texture_size(struct sc_display *display, struct sc_size size) {
return SC_DISPLAY_RESULT_OK; return SC_DISPLAY_RESULT_OK;
} }
static SDL_YUV_CONVERSION_MODE
sc_display_to_sdl_color_range(enum AVColorRange color_range) {
return color_range == AVCOL_RANGE_JPEG ? SDL_YUV_CONVERSION_JPEG
: SDL_YUV_CONVERSION_AUTOMATIC;
}
static bool static bool
sc_display_update_texture_internal(struct sc_display *display, sc_display_update_texture_internal(struct sc_display *display,
const AVFrame *frame) { const AVFrame *frame) {
if (!display->has_frame) {
// First frame
display->has_frame = true;
// Configure YUV color range conversion
SDL_YUV_CONVERSION_MODE sdl_color_range =
sc_display_to_sdl_color_range(frame->color_range);
SDL_SetYUVConversionMode(sdl_color_range);
}
int ret = SDL_UpdateYUVTexture(display->texture, NULL, int ret = SDL_UpdateYUVTexture(display->texture, NULL,
frame->data[0], frame->linesize[0], frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1], frame->data[1], frame->linesize[1],

View file

@ -33,6 +33,8 @@ struct sc_display {
struct sc_size size; struct sc_size size;
AVFrame *frame; AVFrame *frame;
} pending; } pending;
bool has_frame;
}; };
enum sc_display_result { enum sc_display_result {
@ -42,7 +44,8 @@ enum sc_display_result {
}; };
bool bool
sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps); sc_display_init(struct sc_display *display, SDL_Window *window,
SDL_Surface *icon_novideo, bool mipmaps);
void void
sc_display_destroy(struct sc_display *display); sc_display_destroy(struct sc_display *display);

View file

@ -7,3 +7,4 @@
#define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6) #define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6)
#define SC_EVENT_SCREEN_INIT_SIZE (SDL_USEREVENT + 7) #define SC_EVENT_SCREEN_INIT_SIZE (SDL_USEREVENT + 7)
#define SC_EVENT_TIME_LIMIT_REACHED (SDL_USEREVENT + 8) #define SC_EVENT_TIME_LIMIT_REACHED (SDL_USEREVENT + 8)
#define SC_EVENT_CONTROLLER_ERROR (SDL_USEREVENT + 9)

View file

@ -78,7 +78,19 @@ decode_image(const char *path) {
goto close_input; goto close_input;
} }
int stream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
// 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);
if (stream < 0 ) { if (stream < 0 ) {
LOGE("Could not find best image stream"); LOGE("Could not find best image stream");
goto close_input; goto close_input;
@ -86,12 +98,6 @@ decode_image(const char *path) {
AVCodecParameters *params = ctx->streams[stream]->codecpar; AVCodecParameters *params = ctx->streams[stream]->codecpar;
const AVCodec *codec = avcodec_find_decoder(params->codec_id);
if (!codec) {
LOGE("Could not find image decoder");
goto close_input;
}
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec); AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) { if (!codec_ctx) {
LOG_OOM(); LOG_OOM();
@ -111,21 +117,21 @@ decode_image(const char *path) {
AVFrame *frame = av_frame_alloc(); AVFrame *frame = av_frame_alloc();
if (!frame) { if (!frame) {
LOG_OOM(); LOG_OOM();
goto close_codec; goto free_codec_ctx;
} }
AVPacket *packet = av_packet_alloc(); AVPacket *packet = av_packet_alloc();
if (!packet) { if (!packet) {
LOG_OOM(); LOG_OOM();
av_frame_free(&frame); av_frame_free(&frame);
goto close_codec; goto free_codec_ctx;
} }
if (av_read_frame(ctx, packet) < 0) { if (av_read_frame(ctx, packet) < 0) {
LOGE("Could not read frame"); LOGE("Could not read frame");
av_packet_free(&packet); av_packet_free(&packet);
av_frame_free(&frame); av_frame_free(&frame);
goto close_codec; goto free_codec_ctx;
} }
int ret; int ret;
@ -133,22 +139,20 @@ decode_image(const char *path) {
LOGE("Could not send icon packet: %d", ret); LOGE("Could not send icon packet: %d", ret);
av_packet_free(&packet); av_packet_free(&packet);
av_frame_free(&frame); av_frame_free(&frame);
goto close_codec; goto free_codec_ctx;
} }
if ((ret = avcodec_receive_frame(codec_ctx, frame)) != 0) { if ((ret = avcodec_receive_frame(codec_ctx, frame)) != 0) {
LOGE("Could not receive icon frame: %d", ret); LOGE("Could not receive icon frame: %d", ret);
av_packet_free(&packet); av_packet_free(&packet);
av_frame_free(&frame); av_frame_free(&frame);
goto close_codec; goto free_codec_ctx;
} }
av_packet_free(&packet); av_packet_free(&packet);
result = frame; result = frame;
close_codec:
avcodec_close(codec_ctx);
free_codec_ctx: free_codec_ctx:
avcodec_free_context(&codec_ctx); avcodec_free_context(&codec_ctx);
close_input: close_input:

View file

@ -9,6 +9,7 @@
#include <SDL2/SDL_events.h> #include <SDL2/SDL_events.h>
#include "coords.h" #include "coords.h"
#include "options.h"
/* The representation of input events in scrcpy is very close to the SDL API, /* The representation of input events in scrcpy is very close to the SDL API,
* for simplicity. * for simplicity.
@ -437,15 +438,21 @@ sc_mouse_button_from_sdl(uint8_t button) {
static inline uint8_t 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) { const struct sc_mouse_bindings *mb) {
assert(buttons_state < 0x100); // fits in uint8_t assert(buttons_state < 0x100); // fits in uint8_t
uint8_t mask = SC_MOUSE_BUTTON_LEFT; uint8_t mask = SC_MOUSE_BUTTON_LEFT;
if (forward_all_clicks) { if (!mb || mb->right_click == SC_MOUSE_BINDING_CLICK) {
mask |= SC_MOUSE_BUTTON_RIGHT mask |= SC_MOUSE_BUTTON_RIGHT;
| SC_MOUSE_BUTTON_MIDDLE }
| SC_MOUSE_BUTTON_X1 if (!mb || mb->middle_click == SC_MOUSE_BINDING_CLICK) {
| SC_MOUSE_BUTTON_X2; mask |= SC_MOUSE_BUTTON_MIDDLE;
}
if (!mb || mb->click4 == SC_MOUSE_BINDING_CLICK) {
mask |= SC_MOUSE_BUTTON_X1;
}
if (!mb || mb->click5 == SC_MOUSE_BINDING_CLICK) {
mask |= SC_MOUSE_BUTTON_X2;
} }
return buttons_state & mask; return buttons_state & mask;

View file

@ -10,7 +10,7 @@
#define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI) #define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI)
static inline uint16_t static inline uint16_t
to_sdl_mod(unsigned shortcut_mod) { to_sdl_mod(uint8_t shortcut_mod) {
uint16_t sdl_mod = 0; uint16_t sdl_mod = 0;
if (shortcut_mod & SC_SHORTCUT_MOD_LCTRL) { if (shortcut_mod & SC_SHORTCUT_MOD_LCTRL) {
sdl_mod |= KMOD_LCTRL; sdl_mod |= KMOD_LCTRL;
@ -38,15 +38,26 @@ is_shortcut_mod(struct sc_input_manager *im, uint16_t sdl_mod) {
// keep only the relevant modifier keys // keep only the relevant modifier keys
sdl_mod &= SC_SDL_SHORTCUT_MODS_MASK; sdl_mod &= SC_SDL_SHORTCUT_MODS_MASK;
assert(im->sdl_shortcut_mods.count); // at least one shortcut mod pressed?
assert(im->sdl_shortcut_mods.count < SC_MAX_SHORTCUT_MODS); return sdl_mod & im->sdl_shortcut_mods;
for (unsigned i = 0; i < im->sdl_shortcut_mods.count; ++i) { }
if (im->sdl_shortcut_mods.data[i] == sdl_mod) {
return true;
}
}
return false; 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);
}
static inline bool
mouse_bindings_has_secondary_click(const struct sc_mouse_bindings *mb) {
return mb->right_click == SC_MOUSE_BINDING_CLICK
|| mb->middle_click == SC_MOUSE_BINDING_CLICK
|| mb->click4 == SC_MOUSE_BINDING_CLICK
|| mb->click5 == SC_MOUSE_BINDING_CLICK;
} }
void void
@ -64,19 +75,13 @@ sc_input_manager_init(struct sc_input_manager *im,
im->kp = params->kp; im->kp = params->kp;
im->mp = params->mp; im->mp = params->mp;
im->forward_all_clicks = params->forward_all_clicks; im->mouse_bindings = params->mouse_bindings;
im->has_secondary_click =
mouse_bindings_has_secondary_click(&im->mouse_bindings);
im->legacy_paste = params->legacy_paste; im->legacy_paste = params->legacy_paste;
im->clipboard_autosync = params->clipboard_autosync; im->clipboard_autosync = params->clipboard_autosync;
const struct sc_shortcut_mods *shortcut_mods = params->shortcut_mods; im->sdl_shortcut_mods = to_sdl_mod(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_down = false;
im->vfinger_invert_x = false; im->vfinger_invert_x = false;
@ -371,8 +376,8 @@ simulate_virtual_finger(struct sc_input_manager *im,
msg.inject_touch_event.position.screen_size = im->screen->frame_size; msg.inject_touch_event.position.screen_size = im->screen->frame_size;
msg.inject_touch_event.position.point = point; msg.inject_touch_event.position.point = point;
msg.inject_touch_event.pointer_id = msg.inject_touch_event.pointer_id =
im->forward_all_clicks ? POINTER_ID_VIRTUAL_MOUSE im->has_secondary_click ? POINTER_ID_VIRTUAL_MOUSE
: POINTER_ID_VIRTUAL_FINGER; : POINTER_ID_VIRTUAL_FINGER;
msg.inject_touch_event.pressure = up ? 0.0f : 1.0f; msg.inject_touch_event.pressure = up ? 0.0f : 1.0f;
msg.inject_touch_event.action_button = 0; msg.inject_touch_event.action_button = 0;
msg.inject_touch_event.buttons = 0; msg.inject_touch_event.buttons = 0;
@ -402,6 +407,8 @@ sc_input_manager_process_key(struct sc_input_manager *im,
const SDL_KeyboardEvent *event) { const SDL_KeyboardEvent *event) {
// controller is NULL if --no-control is requested // controller is NULL if --no-control is requested
bool control = im->controller; bool control = im->controller;
bool paused = im->screen->paused;
bool video = im->screen->video;
SDL_Keycode keycode = event->keysym.sym; SDL_Keycode keycode = event->keysym.sym;
uint16_t mod = event->keysym.mod; uint16_t mod = event->keysym.mod;
@ -410,7 +417,12 @@ sc_input_manager_process_key(struct sc_input_manager *im,
bool shift = event->keysym.mod & KMOD_SHIFT; bool shift = event->keysym.mod & KMOD_SHIFT;
bool repeat = event->repeat; bool repeat = event->repeat;
bool smod = is_shortcut_mod(im, mod); // 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);
if (down && !repeat) { if (down && !repeat) {
if (keycode == im->last_keycode && mod == im->last_mod) { if (keycode == im->last_keycode && mod == im->last_mod) {
@ -422,68 +434,72 @@ sc_input_manager_process_key(struct sc_input_manager *im,
} }
} }
// The shortcut modifier is pressed if (is_shortcut) {
if (smod) {
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP; enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
switch (keycode) { switch (keycode) {
case SDLK_h: case SDLK_h:
if (im->kp && !shift && !repeat) { if (im->kp && !shift && !repeat && !paused) {
action_home(im, action); action_home(im, action);
} }
return; return;
case SDLK_b: // fall-through case SDLK_b: // fall-through
case SDLK_BACKSPACE: case SDLK_BACKSPACE:
if (im->kp && !shift && !repeat) { if (im->kp && !shift && !repeat && !paused) {
action_back(im, action); action_back(im, action);
} }
return; return;
case SDLK_s: case SDLK_s:
if (im->kp && !shift && !repeat) { if (im->kp && !shift && !repeat && !paused) {
action_app_switch(im, action); action_app_switch(im, action);
} }
return; return;
case SDLK_m: case SDLK_m:
if (im->kp && !shift && !repeat) { if (im->kp && !shift && !repeat && !paused) {
action_menu(im, action); action_menu(im, action);
} }
return; return;
case SDLK_p: case SDLK_p:
if (im->kp && !shift && !repeat) { if (im->kp && !shift && !repeat && !paused) {
action_power(im, action); action_power(im, action);
} }
return; return;
case SDLK_o: case SDLK_o:
if (control && !repeat && down) { if (control && !repeat && down && !paused) {
enum sc_screen_power_mode mode = shift enum sc_screen_power_mode mode = shift
? SC_SCREEN_POWER_MODE_NORMAL ? SC_SCREEN_POWER_MODE_NORMAL
: SC_SCREEN_POWER_MODE_OFF; : SC_SCREEN_POWER_MODE_OFF;
set_screen_power_mode(im, mode); set_screen_power_mode(im, mode);
} }
return; return;
case SDLK_z:
if (video && down && !repeat) {
sc_screen_set_paused(im->screen, !shift);
}
return;
case SDLK_DOWN: case SDLK_DOWN:
if (shift) { if (shift) {
if (!repeat & down) { if (video && !repeat && down) {
apply_orientation_transform(im, apply_orientation_transform(im,
SC_ORIENTATION_FLIP_180); SC_ORIENTATION_FLIP_180);
} }
} else if (im->kp) { } else if (im->kp && !paused) {
// forward repeated events // forward repeated events
action_volume_down(im, action); action_volume_down(im, action);
} }
return; return;
case SDLK_UP: case SDLK_UP:
if (shift) { if (shift) {
if (!repeat & down) { if (video && !repeat && down) {
apply_orientation_transform(im, apply_orientation_transform(im,
SC_ORIENTATION_FLIP_180); SC_ORIENTATION_FLIP_180);
} }
} else if (im->kp) { } else if (im->kp && !paused) {
// forward repeated events // forward repeated events
action_volume_up(im, action); action_volume_up(im, action);
} }
return; return;
case SDLK_LEFT: case SDLK_LEFT:
if (!repeat && down) { if (video && !repeat && down) {
if (shift) { if (shift) {
apply_orientation_transform(im, apply_orientation_transform(im,
SC_ORIENTATION_FLIP_0); SC_ORIENTATION_FLIP_0);
@ -494,7 +510,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
} }
return; return;
case SDLK_RIGHT: case SDLK_RIGHT:
if (!repeat && down) { if (video && !repeat && down) {
if (shift) { if (shift) {
apply_orientation_transform(im, apply_orientation_transform(im,
SC_ORIENTATION_FLIP_0); SC_ORIENTATION_FLIP_0);
@ -505,17 +521,17 @@ sc_input_manager_process_key(struct sc_input_manager *im,
} }
return; return;
case SDLK_c: case SDLK_c:
if (im->kp && !shift && !repeat && down) { if (im->kp && !shift && !repeat && down && !paused) {
get_device_clipboard(im, SC_COPY_KEY_COPY); get_device_clipboard(im, SC_COPY_KEY_COPY);
} }
return; return;
case SDLK_x: case SDLK_x:
if (im->kp && !shift && !repeat && down) { if (im->kp && !shift && !repeat && down && !paused) {
get_device_clipboard(im, SC_COPY_KEY_CUT); get_device_clipboard(im, SC_COPY_KEY_CUT);
} }
return; return;
case SDLK_v: case SDLK_v:
if (im->kp && !repeat && down) { if (im->kp && !repeat && down && !paused) {
if (shift || im->legacy_paste) { if (shift || im->legacy_paste) {
// inject the text as input events // inject the text as input events
clipboard_paste(im); clipboard_paste(im);
@ -527,27 +543,27 @@ sc_input_manager_process_key(struct sc_input_manager *im,
} }
return; return;
case SDLK_f: case SDLK_f:
if (!shift && !repeat && down) { if (video && !shift && !repeat && down) {
sc_screen_switch_fullscreen(im->screen); sc_screen_switch_fullscreen(im->screen);
} }
return; return;
case SDLK_w: case SDLK_w:
if (!shift && !repeat && down) { if (video && !shift && !repeat && down) {
sc_screen_resize_to_fit(im->screen); sc_screen_resize_to_fit(im->screen);
} }
return; return;
case SDLK_g: case SDLK_g:
if (!shift && !repeat && down) { if (video && !shift && !repeat && down) {
sc_screen_resize_to_pixel_perfect(im->screen); sc_screen_resize_to_pixel_perfect(im->screen);
} }
return; return;
case SDLK_i: case SDLK_i:
if (!shift && !repeat && down) { if (video && !shift && !repeat && down) {
switch_fps_counter_state(im); switch_fps_counter_state(im);
} }
return; return;
case SDLK_n: case SDLK_n:
if (control && !repeat && down) { if (control && !repeat && down && !paused) {
if (shift) { if (shift) {
collapse_panels(im); collapse_panels(im);
} else if (im->key_repeat == 0) { } else if (im->key_repeat == 0) {
@ -558,12 +574,12 @@ sc_input_manager_process_key(struct sc_input_manager *im,
} }
return; return;
case SDLK_r: case SDLK_r:
if (control && !shift && !repeat && down) { if (control && !shift && !repeat && down && !paused) {
rotate_device(im); rotate_device(im);
} }
return; return;
case SDLK_k: case SDLK_k:
if (control && !shift && !repeat && down if (control && !shift && !repeat && down && !paused
&& im->kp && im->kp->hid) { && im->kp && im->kp->hid) {
// Only if the current keyboard is hid // Only if the current keyboard is hid
open_hard_keyboard_settings(im); open_hard_keyboard_settings(im);
@ -574,7 +590,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
return; return;
} }
if (!im->kp) { if (!im->kp || paused) {
return; return;
} }
@ -619,29 +635,39 @@ sc_input_manager_process_key(struct sc_input_manager *im,
im->kp->ops->process_key(im->kp, &evt, ack_to_wait); im->kp->ops->process_key(im->kp, &evt, ack_to_wait);
} }
static struct sc_position
sc_input_manager_get_position(struct sc_input_manager *im, int32_t x,
int32_t y) {
if (im->mp->relative_mode) {
// No absolute position
return (struct sc_position) {
.screen_size = {0, 0},
.point = {0, 0},
};
}
return (struct sc_position) {
.screen_size = im->screen->frame_size,
.point = sc_screen_convert_window_to_frame_coords(im->screen, x, y),
};
}
static void static void
sc_input_manager_process_mouse_motion(struct sc_input_manager *im, sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
const SDL_MouseMotionEvent *event) { const SDL_MouseMotionEvent *event) {
if (event->which == SDL_TOUCH_MOUSEID) { if (event->which == SDL_TOUCH_MOUSEID) {
// simulated from touch events, so it's a duplicate // simulated from touch events, so it's a duplicate
return; return;
} }
struct sc_mouse_motion_event evt = { struct sc_mouse_motion_event evt = {
.position = { .position = sc_input_manager_get_position(im, event->x, event->y),
.screen_size = im->screen->frame_size, .pointer_id = im->has_secondary_click ? POINTER_ID_MOUSE
.point = sc_screen_convert_window_to_frame_coords(im->screen, : POINTER_ID_GENERIC_FINGER,
event->x,
event->y),
},
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
: POINTER_ID_GENERIC_FINGER,
.xrel = event->xrel, .xrel = event->xrel,
.yrel = event->yrel, .yrel = event->yrel,
.buttons_state = .buttons_state =
sc_mouse_buttons_state_from_sdl(event->state, sc_mouse_buttons_state_from_sdl(event->state, &im->mouse_bindings),
im->forward_all_clicks),
}; };
assert(im->mp->ops->process_mouse_motion); assert(im->mp->ops->process_mouse_motion);
@ -692,81 +718,109 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
im->mp->ops->process_touch(im->mp, &evt); im->mp->ops->process_touch(im->mp, &evt);
} }
static enum sc_mouse_binding
sc_input_manager_get_binding(const struct sc_mouse_bindings *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 static void
sc_input_manager_process_mouse_button(struct sc_input_manager *im, sc_input_manager_process_mouse_button(struct sc_input_manager *im,
const SDL_MouseButtonEvent *event) { const SDL_MouseButtonEvent *event) {
bool control = im->controller;
if (event->which == SDL_TOUCH_MOUSEID) { if (event->which == SDL_TOUCH_MOUSEID) {
// simulated from touch events, so it's a duplicate // simulated from touch events, so it's a duplicate
return; return;
} }
bool control = im->controller;
bool paused = im->screen->paused;
bool down = event->type == SDL_MOUSEBUTTONDOWN; bool down = event->type == SDL_MOUSEBUTTONDOWN;
if (!im->forward_all_clicks) { if (control && !paused) {
if (control) { enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
if (im->kp && event->button == SDL_BUTTON_X1) { enum sc_mouse_binding binding =
action_app_switch(im, action); sc_input_manager_get_binding(&im->mouse_bindings, event->button);
assert(binding != SC_MOUSE_BINDING_AUTO);
switch (binding) {
case SC_MOUSE_BINDING_DISABLED:
// ignore click
return; return;
} case SC_MOUSE_BINDING_BACK:
if (event->button == SDL_BUTTON_X2 && down) { if (im->kp) {
if (event->clicks < 2) { press_back_or_turn_screen_on(im, action);
expand_notification_panel(im);
} else {
expand_settings_panel(im);
} }
return; return;
} case SC_MOUSE_BINDING_HOME:
if (im->kp && event->button == SDL_BUTTON_RIGHT) { if (im->kp) {
press_back_or_turn_screen_on(im, action); action_home(im, action);
}
return; return;
} case SC_MOUSE_BINDING_APP_SWITCH:
if (im->kp && event->button == SDL_BUTTON_MIDDLE) { if (im->kp) {
action_home(im, action); action_app_switch(im, action);
}
return; return;
} case SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL:
}
// double-click on black borders resize to fit the device screen
if (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) { if (down) {
sc_screen_resize_to_fit(im->screen); if (event->clicks < 2) {
expand_notification_panel(im);
} else {
expand_settings_panel(im);
}
} }
return; return;
} default:
assert(binding == SC_MOUSE_BINDING_CLICK);
break;
} }
// otherwise, send the click event to the device
} }
if (!im->mp) { // 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;
}
}
if (!im->mp || paused) {
return; return;
} }
uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL); uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL);
struct sc_mouse_click_event evt = { struct sc_mouse_click_event evt = {
.position = { .position = sc_input_manager_get_position(im, event->x, event->y),
.screen_size = im->screen->frame_size,
.point = sc_screen_convert_window_to_frame_coords(im->screen,
event->x,
event->y),
},
.action = sc_action_from_sdl_mousebutton_type(event->type), .action = sc_action_from_sdl_mousebutton_type(event->type),
.button = sc_mouse_button_from_sdl(event->button), .button = sc_mouse_button_from_sdl(event->button),
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE .pointer_id = im->has_secondary_click ? POINTER_ID_MOUSE
: POINTER_ID_GENERIC_FINGER, : POINTER_ID_GENERIC_FINGER,
.buttons_state = .buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state,
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, &im->mouse_bindings),
im->forward_all_clicks),
}; };
assert(im->mp->ops->process_mouse_click); assert(im->mp->ops->process_mouse_click);
@ -834,11 +888,7 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
uint32_t buttons = SDL_GetMouseState(&mouse_x, &mouse_y); uint32_t buttons = SDL_GetMouseState(&mouse_x, &mouse_y);
struct sc_mouse_scroll_event evt = { struct sc_mouse_scroll_event evt = {
.position = { .position = sc_input_manager_get_position(im, mouse_x, mouse_y),
.screen_size = im->screen->frame_size,
.point = sc_screen_convert_window_to_frame_coords(im->screen,
mouse_x, mouse_y),
},
#if SDL_VERSION_ATLEAST(2, 0, 18) #if SDL_VERSION_ATLEAST(2, 0, 18)
.hscroll = CLAMP(event->preciseX, -1.0f, 1.0f), .hscroll = CLAMP(event->preciseX, -1.0f, 1.0f),
.vscroll = CLAMP(event->preciseY, -1.0f, 1.0f), .vscroll = CLAMP(event->preciseY, -1.0f, 1.0f),
@ -846,8 +896,8 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
.hscroll = CLAMP(event->x, -1, 1), .hscroll = CLAMP(event->x, -1, 1),
.vscroll = CLAMP(event->y, -1, 1), .vscroll = CLAMP(event->y, -1, 1),
#endif #endif
.buttons_state = .buttons_state = sc_mouse_buttons_state_from_sdl(buttons,
sc_mouse_buttons_state_from_sdl(buttons, im->forward_all_clicks), &im->mouse_bindings),
}; };
im->mp->ops->process_mouse_scroll(im->mp, &evt); im->mp->ops->process_mouse_scroll(im->mp, &evt);
@ -885,9 +935,10 @@ void
sc_input_manager_handle_event(struct sc_input_manager *im, sc_input_manager_handle_event(struct sc_input_manager *im,
const SDL_Event *event) { const SDL_Event *event) {
bool control = im->controller; bool control = im->controller;
bool paused = im->screen->paused;
switch (event->type) { switch (event->type) {
case SDL_TEXTINPUT: case SDL_TEXTINPUT:
if (!im->kp) { if (!im->kp || paused) {
break; break;
} }
sc_input_manager_process_text_input(im, &event->text); sc_input_manager_process_text_input(im, &event->text);
@ -899,13 +950,13 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
sc_input_manager_process_key(im, &event->key); sc_input_manager_process_key(im, &event->key);
break; break;
case SDL_MOUSEMOTION: case SDL_MOUSEMOTION:
if (!im->mp) { if (!im->mp || paused) {
break; break;
} }
sc_input_manager_process_mouse_motion(im, &event->motion); sc_input_manager_process_mouse_motion(im, &event->motion);
break; break;
case SDL_MOUSEWHEEL: case SDL_MOUSEWHEEL:
if (!im->mp) { if (!im->mp || paused) {
break; break;
} }
sc_input_manager_process_mouse_wheel(im, &event->wheel); sc_input_manager_process_mouse_wheel(im, &event->wheel);
@ -919,7 +970,7 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
case SDL_FINGERMOTION: case SDL_FINGERMOTION:
case SDL_FINGERDOWN: case SDL_FINGERDOWN:
case SDL_FINGERUP: case SDL_FINGERUP:
if (!im->mp) { if (!im->mp || paused) {
break; break;
} }
sc_input_manager_process_touch(im, &event->tfinger); sc_input_manager_process_touch(im, &event->tfinger);

View file

@ -22,14 +22,12 @@ struct sc_input_manager {
struct sc_key_processor *kp; struct sc_key_processor *kp;
struct sc_mouse_processor *mp; struct sc_mouse_processor *mp;
bool forward_all_clicks; struct sc_mouse_bindings mouse_bindings;
bool has_secondary_click;
bool legacy_paste; bool legacy_paste;
bool clipboard_autosync; bool clipboard_autosync;
struct { uint16_t sdl_shortcut_mods;
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
} sdl_shortcut_mods;
bool vfinger_down; bool vfinger_down;
bool vfinger_invert_x; bool vfinger_invert_x;
@ -52,10 +50,10 @@ struct sc_input_manager_params {
struct sc_key_processor *kp; struct sc_key_processor *kp;
struct sc_mouse_processor *mp; struct sc_mouse_processor *mp;
bool forward_all_clicks; struct sc_mouse_bindings mouse_bindings;
bool legacy_paste; bool legacy_paste;
bool clipboard_autosync; bool clipboard_autosync;
const struct sc_shortcut_mods *shortcut_mods; uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
}; };
void void

View file

@ -58,17 +58,18 @@ convert_touch_action(enum sc_touch_action action) {
static void static void
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
const struct sc_mouse_motion_event *event) { const struct sc_mouse_motion_event *event) {
if (!event->buttons_state) { struct sc_mouse_sdk *m = DOWNCAST(mp);
if (!m->mouse_hover && !event->buttons_state) {
// Do not send motion events when no click is pressed // Do not send motion events when no click is pressed
return; return;
} }
struct sc_mouse_sdk *m = DOWNCAST(mp);
struct sc_control_msg msg = { struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, .type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = { .inject_touch_event = {
.action = AMOTION_EVENT_ACTION_MOVE, .action = event->buttons_state ? AMOTION_EVENT_ACTION_MOVE
: AMOTION_EVENT_ACTION_HOVER_MOVE,
.pointer_id = event->pointer_id, .pointer_id = event->pointer_id,
.position = event->position, .position = event->position,
.pressure = 1.f, .pressure = 1.f,
@ -145,8 +146,10 @@ sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
} }
void void
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller) { sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller,
bool mouse_hover) {
m->controller = controller; m->controller = controller;
m->mouse_hover = mouse_hover;
static const struct sc_mouse_processor_ops ops = { static const struct sc_mouse_processor_ops ops = {
.process_mouse_motion = sc_mouse_processor_process_mouse_motion, .process_mouse_motion = sc_mouse_processor_process_mouse_motion,

View file

@ -13,9 +13,11 @@ struct sc_mouse_sdk {
struct sc_mouse_processor mouse_processor; // mouse processor trait struct sc_mouse_processor mouse_processor; // mouse processor trait
struct sc_controller *controller; struct sc_controller *controller;
bool mouse_hover;
}; };
void void
sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller); sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller,
bool mouse_hover);
#endif #endif

View file

@ -23,6 +23,12 @@ const struct scrcpy_options scrcpy_options_default = {
.record_format = SC_RECORD_FORMAT_AUTO, .record_format = SC_RECORD_FORMAT_AUTO,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO, .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO,
.mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO, .mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO,
.mouse_bindings = {
.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, .camera_facing = SC_CAMERA_FACING_ANY,
.port_range = { .port_range = {
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, .first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
@ -30,10 +36,7 @@ const struct scrcpy_options scrcpy_options_default = {
}, },
.tunnel_host = 0, .tunnel_host = 0,
.tunnel_port = 0, .tunnel_port = 0,
.shortcut_mods = { .shortcut_mods = SC_SHORTCUT_MOD_LALT | SC_SHORTCUT_MOD_LSUPER,
.data = {SC_SHORTCUT_MOD_LALT, SC_SHORTCUT_MOD_LSUPER},
.count = 2,
},
.max_size = 0, .max_size = 0,
.video_bit_rate = 0, .video_bit_rate = 0,
.audio_bit_rate = 0, .audio_bit_rate = 0,
@ -71,7 +74,6 @@ const struct scrcpy_options scrcpy_options_default = {
.force_adb_forward = false, .force_adb_forward = false,
.disable_screensaver = false, .disable_screensaver = false,
.forward_key_repeat = true, .forward_key_repeat = true,
.forward_all_clicks = false,
.legacy_paste = false, .legacy_paste = false,
.power_off_on_close = false, .power_off_on_close = false,
.clipboard_autosync = true, .clipboard_autosync = true,
@ -89,6 +91,8 @@ const struct scrcpy_options scrcpy_options_default = {
.kill_adb_on_close = false, .kill_adb_on_close = false,
.camera_high_speed = false, .camera_high_speed = false,
.list = 0, .list = 0,
.window = true,
.mouse_hover = true,
}; };
enum sc_orientation enum sc_orientation

View file

@ -155,6 +155,23 @@ enum sc_mouse_input_mode {
SC_MOUSE_INPUT_MODE_AOA, 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_bindings {
enum sc_mouse_binding right_click;
enum sc_mouse_binding middle_click;
enum sc_mouse_binding click4;
enum sc_mouse_binding click5;
};
enum sc_key_inject_mode { enum sc_key_inject_mode {
// Inject special keys, letters and space as key events. // Inject special keys, letters and space as key events.
// Inject numbers and punctuation as text events. // Inject numbers and punctuation as text events.
@ -169,8 +186,6 @@ enum sc_key_inject_mode {
SC_KEY_INJECT_MODE_RAW, SC_KEY_INJECT_MODE_RAW,
}; };
#define SC_MAX_SHORTCUT_MODS 8
enum sc_shortcut_mod { enum sc_shortcut_mod {
SC_SHORTCUT_MOD_LCTRL = 1 << 0, SC_SHORTCUT_MOD_LCTRL = 1 << 0,
SC_SHORTCUT_MOD_RCTRL = 1 << 1, SC_SHORTCUT_MOD_RCTRL = 1 << 1,
@ -180,11 +195,6 @@ enum sc_shortcut_mod {
SC_SHORTCUT_MOD_RSUPER = 1 << 5, SC_SHORTCUT_MOD_RSUPER = 1 << 5,
}; };
struct sc_shortcut_mods {
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
};
struct sc_port_range { struct sc_port_range {
uint16_t first; uint16_t first;
uint16_t last; uint16_t last;
@ -215,11 +225,12 @@ struct scrcpy_options {
enum sc_record_format record_format; enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode; enum sc_keyboard_input_mode keyboard_input_mode;
enum sc_mouse_input_mode mouse_input_mode; enum sc_mouse_input_mode mouse_input_mode;
struct sc_mouse_bindings mouse_bindings;
enum sc_camera_facing camera_facing; enum sc_camera_facing camera_facing;
struct sc_port_range port_range; struct sc_port_range port_range;
uint32_t tunnel_host; uint32_t tunnel_host;
uint16_t tunnel_port; uint16_t tunnel_port;
struct sc_shortcut_mods shortcut_mods; uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
uint16_t max_size; uint16_t max_size;
uint32_t video_bit_rate; uint32_t video_bit_rate;
uint32_t audio_bit_rate; uint32_t audio_bit_rate;
@ -257,7 +268,6 @@ struct scrcpy_options {
bool force_adb_forward; bool force_adb_forward;
bool disable_screensaver; bool disable_screensaver;
bool forward_key_repeat; bool forward_key_repeat;
bool forward_all_clicks;
bool legacy_paste; bool legacy_paste;
bool power_off_on_close; bool power_off_on_close;
bool clipboard_autosync; bool clipboard_autosync;
@ -279,6 +289,8 @@ struct scrcpy_options {
#define SC_OPTION_LIST_CAMERAS 0x4 #define SC_OPTION_LIST_CAMERAS 0x4
#define SC_OPTION_LIST_CAMERA_SIZES 0x8 #define SC_OPTION_LIST_CAMERA_SIZES 0x8
uint8_t list; uint8_t list;
bool window;
bool mouse_hover;
}; };
extern const struct scrcpy_options scrcpy_options_default; extern const struct scrcpy_options scrcpy_options_default;

View file

@ -10,7 +10,8 @@
#include "util/str.h" #include "util/str.h"
bool bool
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket) { sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
const struct sc_receiver_callbacks *cbs, void *cbs_userdata) {
bool ok = sc_mutex_init(&receiver->mutex); bool ok = sc_mutex_init(&receiver->mutex);
if (!ok) { if (!ok) {
return false; return false;
@ -20,6 +21,10 @@ sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket) {
receiver->acksync = NULL; receiver->acksync = NULL;
receiver->uhid_devices = NULL; receiver->uhid_devices = NULL;
assert(cbs && cbs->on_error);
receiver->cbs = cbs;
receiver->cbs_userdata = cbs_userdata;
return true; return true;
} }
@ -152,6 +157,8 @@ run_receiver(void *data) {
} }
} }
receiver->cbs->on_error(receiver, receiver->cbs_userdata);
return 0; return 0;
} }

View file

@ -19,10 +19,18 @@ struct sc_receiver {
struct sc_acksync *acksync; struct sc_acksync *acksync;
struct sc_uhid_devices *uhid_devices; struct sc_uhid_devices *uhid_devices;
const struct sc_receiver_callbacks *cbs;
void *cbs_userdata;
};
struct sc_receiver_callbacks {
void (*on_error)(struct sc_receiver *receiver, void *userdata);
}; };
bool bool
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket); sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
const struct sc_receiver_callbacks *cbs, void *cbs_userdata);
void void
sc_receiver_destroy(struct sc_receiver *receiver); sc_receiver_destroy(struct sc_receiver *receiver);

View file

@ -174,6 +174,9 @@ event_loop(struct scrcpy *s) {
case SC_EVENT_DEMUXER_ERROR: case SC_EVENT_DEMUXER_ERROR:
LOGE("Demuxer error"); LOGE("Demuxer error");
return SCRCPY_EXIT_FAILURE; return SCRCPY_EXIT_FAILURE;
case SC_EVENT_CONTROLLER_ERROR:
LOGE("Controller error");
return SCRCPY_EXIT_FAILURE;
case SC_EVENT_RECORDER_ERROR: case SC_EVENT_RECORDER_ERROR:
LOGE("Recorder error"); LOGE("Recorder error");
return SCRCPY_EXIT_FAILURE; return SCRCPY_EXIT_FAILURE;
@ -265,6 +268,16 @@ sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer,
} }
} }
static void
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;
PUSH_EVENT(SC_EVENT_CONTROLLER_ERROR);
}
static void static void
sc_server_on_connection_failed(struct sc_server *server, void *userdata) { sc_server_on_connection_failed(struct sc_server *server, void *userdata) {
(void) server; (void) server;
@ -408,7 +421,7 @@ scrcpy(struct scrcpy_options *options) {
return SCRCPY_EXIT_FAILURE; return SCRCPY_EXIT_FAILURE;
} }
if (options->video_playback) { if (options->window) {
// Set hints before starting the server thread to avoid race conditions // Set hints before starting the server thread to avoid race conditions
// in SDL // in SDL
sdl_set_hints(options->render_driver); sdl_set_hints(options->render_driver);
@ -430,7 +443,7 @@ scrcpy(struct scrcpy_options *options) {
assert(!options->video_playback || options->video); assert(!options->video_playback || options->video);
assert(!options->audio_playback || options->audio); assert(!options->audio_playback || options->audio);
if (options->video_playback || if (options->window ||
(options->control && options->clipboard_autosync)) { (options->control && options->clipboard_autosync)) {
// Initialize the video subsystem even if --no-video or // Initialize the video subsystem even if --no-video or
// --no-video-playback is passed so that clipboard synchronization // --no-video-playback is passed so that clipboard synchronization
@ -553,7 +566,12 @@ scrcpy(struct scrcpy_options *options) {
struct sc_mouse_processor *mp = NULL; struct sc_mouse_processor *mp = NULL;
if (options->control) { if (options->control) {
if (!sc_controller_init(&s->controller, s->server.control_socket)) { static const struct sc_controller_callbacks controller_cbs = {
.on_error = sc_controller_on_error,
};
if (!sc_controller_init(&s->controller, s->server.control_socket,
&controller_cbs, NULL)) {
goto end; goto end;
} }
controller_initialized = true; controller_initialized = true;
@ -663,7 +681,8 @@ scrcpy(struct scrcpy_options *options) {
} }
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) { if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
sc_mouse_sdk_init(&s->mouse_sdk, &s->controller); sc_mouse_sdk_init(&s->mouse_sdk, &s->controller,
options->mouse_hover);
mp = &s->mouse_sdk.mouse_processor; mp = &s->mouse_sdk.mouse_processor;
} else if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_UHID) { } else if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_UHID) {
bool ok = sc_mouse_uhid_init(&s->mouse_uhid, &s->controller); bool ok = sc_mouse_uhid_init(&s->mouse_uhid, &s->controller);
@ -684,19 +703,20 @@ scrcpy(struct scrcpy_options *options) {
// There is a controller if and only if control is enabled // There is a controller if and only if control is enabled
assert(options->control == !!controller); assert(options->control == !!controller);
if (options->video_playback) { if (options->window) {
const char *window_title = const char *window_title =
options->window_title ? options->window_title : info->device_name; options->window_title ? options->window_title : info->device_name;
struct sc_screen_params screen_params = { struct sc_screen_params screen_params = {
.video = options->video_playback,
.controller = controller, .controller = controller,
.fp = fp, .fp = fp,
.kp = kp, .kp = kp,
.mp = mp, .mp = mp,
.forward_all_clicks = options->forward_all_clicks, .mouse_bindings = options->mouse_bindings,
.legacy_paste = options->legacy_paste, .legacy_paste = options->legacy_paste,
.clipboard_autosync = options->clipboard_autosync, .clipboard_autosync = options->clipboard_autosync,
.shortcut_mods = &options->shortcut_mods, .shortcut_mods = options->shortcut_mods,
.window_title = window_title, .window_title = window_title,
.always_on_top = options->always_on_top, .always_on_top = options->always_on_top,
.window_x = options->window_x, .window_x = options->window_x,
@ -710,12 +730,15 @@ scrcpy(struct scrcpy_options *options) {
.start_fps_counter = options->start_fps_counter, .start_fps_counter = options->start_fps_counter,
}; };
struct sc_frame_source *src = &s->video_decoder.frame_source; struct sc_frame_source *src;
if (options->display_buffer) { if (options->video_playback) {
sc_delay_buffer_init(&s->display_buffer, options->display_buffer, src = &s->video_decoder.frame_source;
true); if (options->display_buffer) {
sc_frame_source_add_sink(src, &s->display_buffer.frame_sink); sc_delay_buffer_init(&s->display_buffer,
src = &s->display_buffer.frame_source; 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)) { if (!sc_screen_init(&s->screen, &screen_params)) {
@ -723,7 +746,9 @@ scrcpy(struct scrcpy_options *options) {
} }
screen_initialized = true; screen_initialized = true;
sc_frame_source_add_sink(src, &s->screen.frame_sink); if (options->video_playback) {
sc_frame_source_add_sink(src, &s->screen.frame_sink);
}
} }
if (options->audio_playback) { if (options->audio_playback) {
@ -805,9 +830,12 @@ scrcpy(struct scrcpy_options *options) {
ret = event_loop(s); ret = event_loop(s);
LOGD("quit..."); LOGD("quit...");
// Close the window immediately on closing, because screen_destroy() may if (options->video_playback) {
// only be called once the video demuxer thread is joined (it may take time) // Close the window immediately on closing, because screen_destroy()
sc_screen_hide_window(&s->screen); // may only be called once the video demuxer thread is joined (it may
// take time)
sc_screen_hide_window(&s->screen);
}
end: end:
if (timeout_started) { if (timeout_started) {

View file

@ -205,6 +205,8 @@ sc_screen_toggle_mouse_capture(struct sc_screen *screen) {
static void static void
sc_screen_update_content_rect(struct sc_screen *screen) { sc_screen_update_content_rect(struct sc_screen *screen) {
assert(screen->video);
int dw; int dw;
int dh; int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh); SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
@ -246,6 +248,8 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
// changed, so that the content rectangle is recomputed // changed, so that the content rectangle is recomputed
static void static void
sc_screen_render(struct sc_screen *screen, bool update_content_rect) { sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
assert(screen->video);
if (update_content_rect) { if (update_content_rect) {
sc_screen_update_content_rect(screen); sc_screen_update_content_rect(screen);
} }
@ -255,6 +259,13 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
(void) res; // any error already logged (void) res; // any error already logged
} }
static void
sc_screen_render_novideo(struct sc_screen *screen) {
enum sc_display_result res =
sc_display_render(&screen->display, NULL, SC_ORIENTATION_0);
(void) res; // any error already logged
}
#if defined(__APPLE__) || defined(__WINDOWS__) #if defined(__APPLE__) || defined(__WINDOWS__)
# define CONTINUOUS_RESIZING_WORKAROUND # define CONTINUOUS_RESIZING_WORKAROUND
#endif #endif
@ -268,6 +279,8 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
static int static int
event_watcher(void *data, SDL_Event *event) { event_watcher(void *data, SDL_Event *event) {
struct sc_screen *screen = data; struct sc_screen *screen = data;
assert(screen->video);
if (event->type == SDL_WINDOWEVENT if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_RESIZED) { && event->window.event == SDL_WINDOWEVENT_RESIZED) {
// In practice, it seems to always be called from the same thread in // In practice, it seems to always be called from the same thread in
@ -326,6 +339,7 @@ sc_screen_frame_sink_close(struct sc_frame_sink *sink) {
static bool static bool
sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
struct sc_screen *screen = DOWNCAST(sink); struct sc_screen *screen = DOWNCAST(sink);
assert(screen->video);
bool previous_skipped; bool previous_skipped;
bool ok = sc_frame_buffer_push(&screen->fb, frame, &previous_skipped); bool ok = sc_frame_buffer_push(&screen->fb, frame, &previous_skipped);
@ -362,6 +376,11 @@ sc_screen_init(struct sc_screen *screen,
screen->maximized = false; screen->maximized = false;
screen->minimized = false; screen->minimized = false;
screen->mouse_capture_key_pressed = 0; screen->mouse_capture_key_pressed = 0;
screen->paused = false;
screen->resume_frame = NULL;
screen->orientation = SC_ORIENTATION_0;
screen->video = params->video;
screen->req.x = params->window_x; screen->req.x = params->window_x;
screen->req.y = params->window_y; screen->req.y = params->window_y;
@ -379,41 +398,75 @@ sc_screen_init(struct sc_screen *screen,
goto error_destroy_frame_buffer; goto error_destroy_frame_buffer;
} }
screen->orientation = params->orientation; if (screen->video) {
if (screen->orientation != SC_ORIENTATION_0) { screen->orientation = params->orientation;
LOGI("Initial display orientation set to %s", if (screen->orientation != SC_ORIENTATION_0) {
sc_orientation_get_name(screen->orientation)); LOGI("Initial display orientation set to %s",
sc_orientation_get_name(screen->orientation));
}
} }
uint32_t window_flags = SDL_WINDOW_HIDDEN uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI;
| SDL_WINDOW_RESIZABLE
| SDL_WINDOW_ALLOW_HIGHDPI;
if (params->always_on_top) { if (params->always_on_top) {
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP; window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
} }
if (params->window_borderless) { if (params->window_borderless) {
window_flags |= SDL_WINDOW_BORDERLESS; window_flags |= SDL_WINDOW_BORDERLESS;
} }
if (params->video) {
// The window will be shown on first frame
window_flags |= SDL_WINDOW_HIDDEN
| SDL_WINDOW_RESIZABLE;
}
const char *title = params->window_title;
assert(title);
int x = SDL_WINDOWPOS_UNDEFINED;
int y = SDL_WINDOWPOS_UNDEFINED;
int width = 256;
int height = 256;
if (params->window_x != SC_WINDOW_POSITION_UNDEFINED) {
x = params->window_x;
}
if (params->window_y != SC_WINDOW_POSITION_UNDEFINED) {
y = params->window_y;
}
if (params->window_width) {
width = params->window_width;
}
if (params->window_height) {
height = params->window_height;
}
// The window will be positioned and sized on first video frame // The window will be positioned and sized on first video frame
screen->window = screen->window = SDL_CreateWindow(title, x, y, width, height, window_flags);
SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags);
if (!screen->window) { if (!screen->window) {
LOGE("Could not create window: %s", SDL_GetError()); LOGE("Could not create window: %s", SDL_GetError());
goto error_destroy_fps_counter; goto error_destroy_fps_counter;
} }
ok = sc_display_init(&screen->display, screen->window, params->mipmaps);
if (!ok) {
goto error_destroy_window;
}
SDL_Surface *icon = scrcpy_icon_load(); SDL_Surface *icon = scrcpy_icon_load();
if (icon) { if (icon) {
SDL_SetWindowIcon(screen->window, icon); SDL_SetWindowIcon(screen->window, icon);
scrcpy_icon_destroy(icon); } else if (params->video) {
} else { // just a warning
LOGW("Could not load icon"); LOGW("Could not load icon");
} else {
// without video, the icon is used as window content, it must be present
LOGE("Could not load icon");
goto error_destroy_fps_counter;
}
SDL_Surface *icon_novideo = params->video ? NULL : icon;
bool mipmaps = params->video && params->mipmaps;
ok = sc_display_init(&screen->display, screen->window, icon_novideo,
mipmaps);
if (icon) {
scrcpy_icon_destroy(icon);
}
if (!ok) {
goto error_destroy_window;
} }
screen->frame = av_frame_alloc(); screen->frame = av_frame_alloc();
@ -428,7 +481,7 @@ sc_screen_init(struct sc_screen *screen,
.screen = screen, .screen = screen,
.kp = params->kp, .kp = params->kp,
.mp = params->mp, .mp = params->mp,
.forward_all_clicks = params->forward_all_clicks, .mouse_bindings = params->mouse_bindings,
.legacy_paste = params->legacy_paste, .legacy_paste = params->legacy_paste,
.clipboard_autosync = params->clipboard_autosync, .clipboard_autosync = params->clipboard_autosync,
.shortcut_mods = params->shortcut_mods, .shortcut_mods = params->shortcut_mods,
@ -437,7 +490,9 @@ sc_screen_init(struct sc_screen *screen,
sc_input_manager_init(&screen->im, &im_params); sc_input_manager_init(&screen->im, &im_params);
#ifdef CONTINUOUS_RESIZING_WORKAROUND #ifdef CONTINUOUS_RESIZING_WORKAROUND
SDL_AddEventWatch(event_watcher, screen); if (screen->video) {
SDL_AddEventWatch(event_watcher, screen);
}
#endif #endif
static const struct sc_frame_sink_ops ops = { static const struct sc_frame_sink_ops ops = {
@ -452,6 +507,11 @@ sc_screen_init(struct sc_screen *screen,
screen->open = false; screen->open = false;
#endif #endif
if (!screen->video && sc_screen_is_relative_mode(screen)) {
// Capture mouse immediately if video mirroring is disabled
sc_screen_set_mouse_capture(screen, true);
}
return true; return true;
error_destroy_display: error_destroy_display:
@ -522,6 +582,8 @@ sc_screen_destroy(struct sc_screen *screen) {
static void static void
resize_for_content(struct sc_screen *screen, struct sc_size old_content_size, resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
struct sc_size new_content_size) { struct sc_size new_content_size) {
assert(screen->video);
struct sc_size window_size = get_window_size(screen); struct sc_size window_size = get_window_size(screen);
struct sc_size target_size = { struct sc_size target_size = {
.width = (uint32_t) window_size.width * new_content_size.width .width = (uint32_t) window_size.width * new_content_size.width
@ -535,6 +597,8 @@ resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
static void static void
set_content_size(struct sc_screen *screen, struct sc_size new_content_size) { set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
assert(screen->video);
if (!screen->fullscreen && !screen->maximized && !screen->minimized) { if (!screen->fullscreen && !screen->maximized && !screen->minimized) {
resize_for_content(screen, screen->content_size, new_content_size); resize_for_content(screen, screen->content_size, new_content_size);
} else if (!screen->resize_pending) { } else if (!screen->resize_pending) {
@ -549,6 +613,8 @@ set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
static void static void
apply_pending_resize(struct sc_screen *screen) { apply_pending_resize(struct sc_screen *screen) {
assert(screen->video);
assert(!screen->fullscreen); assert(!screen->fullscreen);
assert(!screen->maximized); assert(!screen->maximized);
assert(!screen->minimized); assert(!screen->minimized);
@ -562,6 +628,8 @@ apply_pending_resize(struct sc_screen *screen) {
void void
sc_screen_set_orientation(struct sc_screen *screen, sc_screen_set_orientation(struct sc_screen *screen,
enum sc_orientation orientation) { enum sc_orientation orientation) {
assert(screen->video);
if (orientation == screen->orientation) { if (orientation == screen->orientation) {
return; return;
} }
@ -596,6 +664,8 @@ sc_screen_init_size(struct sc_screen *screen) {
// recreate the texture and resize the window if the frame size has changed // recreate the texture and resize the window if the frame size has changed
static enum sc_display_result static enum sc_display_result
prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) { prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
assert(screen->video);
if (screen->frame_size.width == new_frame_size.width if (screen->frame_size.width == new_frame_size.width
&& screen->frame_size.height == new_frame_size.height) { && screen->frame_size.height == new_frame_size.height) {
return SC_DISPLAY_RESULT_OK; return SC_DISPLAY_RESULT_OK;
@ -614,13 +684,12 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
} }
static bool static bool
sc_screen_update_frame(struct sc_screen *screen) { sc_screen_apply_frame(struct sc_screen *screen) {
av_frame_unref(screen->frame); assert(screen->video);
sc_frame_buffer_consume(&screen->fb, screen->frame);
AVFrame *frame = screen->frame;
sc_fps_counter_add_rendered_frame(&screen->fps_counter); sc_fps_counter_add_rendered_frame(&screen->fps_counter);
AVFrame *frame = screen->frame;
struct sc_size new_frame_size = {frame->width, frame->height}; struct sc_size new_frame_size = {frame->width, frame->height};
enum sc_display_result res = prepare_for_frame(screen, new_frame_size); enum sc_display_result res = prepare_for_frame(screen, new_frame_size);
if (res == SC_DISPLAY_RESULT_ERROR) { if (res == SC_DISPLAY_RESULT_ERROR) {
@ -655,8 +724,62 @@ sc_screen_update_frame(struct sc_screen *screen) {
return true; return true;
} }
static bool
sc_screen_update_frame(struct sc_screen *screen) {
assert(screen->video);
if (screen->paused) {
if (!screen->resume_frame) {
screen->resume_frame = av_frame_alloc();
if (!screen->resume_frame) {
LOG_OOM();
return false;
}
} else {
av_frame_unref(screen->resume_frame);
}
sc_frame_buffer_consume(&screen->fb, screen->resume_frame);
return true;
}
av_frame_unref(screen->frame);
sc_frame_buffer_consume(&screen->fb, screen->frame);
return sc_screen_apply_frame(screen);
}
void
sc_screen_set_paused(struct sc_screen *screen, bool paused) {
assert(screen->video);
if (!paused && !screen->paused) {
// nothing to do
return;
}
if (screen->paused && screen->resume_frame) {
// If display screen was paused, refresh the frame immediately, even if
// the new state is also paused.
av_frame_free(&screen->frame);
screen->frame = screen->resume_frame;
screen->resume_frame = NULL;
sc_screen_apply_frame(screen);
}
if (!paused) {
LOGI("Display screen unpaused");
} else if (!screen->paused) {
LOGI("Display screen paused");
} else {
LOGI("Display screen re-paused");
}
screen->paused = paused;
}
void void
sc_screen_switch_fullscreen(struct sc_screen *screen) { sc_screen_switch_fullscreen(struct sc_screen *screen) {
assert(screen->video);
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP; uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
if (SDL_SetWindowFullscreen(screen->window, new_mode)) { if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
LOGW("Could not switch fullscreen mode: %s", SDL_GetError()); LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
@ -674,6 +797,8 @@ sc_screen_switch_fullscreen(struct sc_screen *screen) {
void void
sc_screen_resize_to_fit(struct sc_screen *screen) { sc_screen_resize_to_fit(struct sc_screen *screen) {
assert(screen->video);
if (screen->fullscreen || screen->maximized || screen->minimized) { if (screen->fullscreen || screen->maximized || screen->minimized) {
return; return;
} }
@ -698,6 +823,8 @@ sc_screen_resize_to_fit(struct sc_screen *screen) {
void void
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) { sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
assert(screen->video);
if (screen->fullscreen || screen->minimized) { if (screen->fullscreen || screen->minimized) {
return; return;
} }
@ -741,6 +868,13 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
return true; return true;
} }
case SDL_WINDOWEVENT: case SDL_WINDOWEVENT:
if (!screen->video
&& event->window.event == SDL_WINDOWEVENT_EXPOSED) {
sc_screen_render_novideo(screen);
}
// !video implies !has_frame
assert(screen->video || !screen->has_frame);
if (!screen->has_frame) { if (!screen->has_frame) {
// Do nothing // Do nothing
return true; return true;
@ -844,6 +978,8 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
struct sc_point struct sc_point
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen, sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y) { int32_t x, int32_t y) {
assert(screen->video);
enum sc_orientation orientation = screen->orientation; enum sc_orientation orientation = screen->orientation;
int32_t w = screen->content_size.width; int32_t w = screen->content_size.width;

View file

@ -26,6 +26,8 @@ struct sc_screen {
bool open; // track the open/close state to assert correct behavior bool open; // track the open/close state to assert correct behavior
#endif #endif
bool video;
struct sc_display display; struct sc_display display;
struct sc_input_manager im; struct sc_input_manager im;
struct sc_frame_buffer fb; struct sc_frame_buffer fb;
@ -64,18 +66,23 @@ struct sc_screen {
SDL_Keycode mouse_capture_key_pressed; SDL_Keycode mouse_capture_key_pressed;
AVFrame *frame; AVFrame *frame;
bool paused;
AVFrame *resume_frame;
}; };
struct sc_screen_params { struct sc_screen_params {
bool video;
struct sc_controller *controller; struct sc_controller *controller;
struct sc_file_pusher *fp; struct sc_file_pusher *fp;
struct sc_key_processor *kp; struct sc_key_processor *kp;
struct sc_mouse_processor *mp; struct sc_mouse_processor *mp;
bool forward_all_clicks; struct sc_mouse_bindings mouse_bindings;
bool legacy_paste; bool legacy_paste;
bool clipboard_autosync; bool clipboard_autosync;
const struct sc_shortcut_mods *shortcut_mods; uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values
const char *window_title; const char *window_title;
bool always_on_top; bool always_on_top;
@ -135,6 +142,10 @@ void
sc_screen_set_orientation(struct sc_screen *screen, sc_screen_set_orientation(struct sc_screen *screen,
enum sc_orientation orientation); enum sc_orientation orientation);
// set the display pause state
void
sc_screen_set_paused(struct sc_screen *screen, bool paused);
// react to SDL events // react to SDL events
// If this function returns false, scrcpy must exit with an error. // If this function returns false, scrcpy must exit with an error.
bool bool

View file

@ -176,6 +176,8 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags,
free(lpAttributeList); free(lpAttributeList);
} }
CloseHandle(pi.hThread);
// These handles are used by the child process, close them for this process // These handles are used by the child process, close them for this process
if (pin) { if (pin) {
CloseHandle(stdin_read_handle); CloseHandle(stdin_read_handle);

View file

@ -169,7 +169,7 @@ sc_screen_otg_process_mouse_motion(struct sc_screen_otg *screen,
// .position not used for HID events // .position not used for HID events
.xrel = event->xrel, .xrel = event->xrel,
.yrel = event->yrel, .yrel = event->yrel,
.buttons_state = sc_mouse_buttons_state_from_sdl(event->state, true), .buttons_state = sc_mouse_buttons_state_from_sdl(event->state, NULL),
}; };
assert(mp->ops->process_mouse_motion); assert(mp->ops->process_mouse_motion);
@ -189,7 +189,7 @@ sc_screen_otg_process_mouse_button(struct sc_screen_otg *screen,
.action = sc_action_from_sdl_mousebutton_type(event->type), .action = sc_action_from_sdl_mousebutton_type(event->type),
.button = sc_mouse_button_from_sdl(event->button), .button = sc_mouse_button_from_sdl(event->button),
.buttons_state = .buttons_state =
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, true), sc_mouse_buttons_state_from_sdl(sdl_buttons_state, NULL),
}; };
assert(mp->ops->process_mouse_click); assert(mp->ops->process_mouse_click);
@ -209,7 +209,7 @@ sc_screen_otg_process_mouse_wheel(struct sc_screen_otg *screen,
.hscroll = event->x, .hscroll = event->x,
.vscroll = event->y, .vscroll = event->y,
.buttons_state = .buttons_state =
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, true), sc_mouse_buttons_state_from_sdl(sdl_buttons_state, NULL),
}; };
assert(mp->ops->process_mouse_scroll); assert(mp->ops->process_mouse_scroll);

View file

@ -46,6 +46,9 @@ sc_audiobuf_read(struct sc_audiobuf *buf, void *to_, uint32_t samples_count) {
uint32_t head = atomic_load_explicit(&buf->head, memory_order_acquire); uint32_t head = atomic_load_explicit(&buf->head, memory_order_acquire);
uint32_t can_read = (buf->alloc_size + head - tail) % buf->alloc_size; uint32_t can_read = (buf->alloc_size + head - tail) % buf->alloc_size;
if (!can_read) {
return 0;
}
if (samples_count > can_read) { if (samples_count > can_read) {
samples_count = can_read; samples_count = can_read;
} }
@ -86,6 +89,9 @@ sc_audiobuf_write(struct sc_audiobuf *buf, const void *from_,
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire); uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire);
uint32_t can_write = (buf->alloc_size + tail - head - 1) % buf->alloc_size; uint32_t can_write = (buf->alloc_size + tail - head - 1) % buf->alloc_size;
if (!can_write) {
return 0;
}
if (samples_count > can_write) { if (samples_count > can_write) {
samples_count = can_write; samples_count = can_write;
} }

View file

@ -240,7 +240,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) {
vs->frame = av_frame_alloc(); vs->frame = av_frame_alloc();
if (!vs->frame) { if (!vs->frame) {
LOG_OOM(); LOG_OOM();
goto error_avcodec_close; goto error_avcodec_free_context;
} }
vs->packet = av_packet_alloc(); vs->packet = av_packet_alloc();
@ -268,8 +268,6 @@ error_av_packet_free:
av_packet_free(&vs->packet); av_packet_free(&vs->packet);
error_av_frame_free: error_av_frame_free:
av_frame_free(&vs->frame); av_frame_free(&vs->frame);
error_avcodec_close:
avcodec_close(vs->encoder_ctx);
error_avcodec_free_context: error_avcodec_free_context:
avcodec_free_context(&vs->encoder_ctx); avcodec_free_context(&vs->encoder_ctx);
error_avio_close: error_avio_close:
@ -297,7 +295,6 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
av_packet_free(&vs->packet); av_packet_free(&vs->packet);
av_frame_free(&vs->frame); av_frame_free(&vs->frame);
avcodec_close(vs->encoder_ctx);
avcodec_free_context(&vs->encoder_ctx); avcodec_free_context(&vs->encoder_ctx);
avio_close(vs->format_ctx->pb); avio_close(vs->format_ctx->pb);
avformat_free_context(vs->format_ctx); avformat_free_context(vs->format_ctx);

View file

@ -124,32 +124,22 @@ static void test_options2(void) {
} }
static void test_parse_shortcut_mods(void) { static void test_parse_shortcut_mods(void) {
struct sc_shortcut_mods mods; uint8_t mods;
bool ok; bool ok;
ok = sc_parse_shortcut_mods("lctrl", &mods); ok = sc_parse_shortcut_mods("lctrl", &mods);
assert(ok); assert(ok);
assert(mods.count == 1); assert(mods == SC_SHORTCUT_MOD_LCTRL);
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); ok = sc_parse_shortcut_mods("rctrl,lalt", &mods);
assert(ok); assert(ok);
assert(mods.count == 2); assert(mods == (SC_SHORTCUT_MOD_RCTRL | SC_SHORTCUT_MOD_LALT));
assert(mods.data[0] == SC_SHORTCUT_MOD_RCTRL);
assert(mods.data[1] == SC_SHORTCUT_MOD_LALT);
ok = sc_parse_shortcut_mods("lsuper,rsuper+lalt,lctrl+rctrl+ralt", &mods); ok = sc_parse_shortcut_mods("lsuper,rsuper,lctrl", &mods);
assert(ok); assert(ok);
assert(mods.count == 3); assert(mods == (SC_SHORTCUT_MOD_LSUPER
assert(mods.data[0] == SC_SHORTCUT_MOD_LSUPER); | SC_SHORTCUT_MOD_RSUPER
assert(mods.data[1] == (SC_SHORTCUT_MOD_RSUPER | SC_SHORTCUT_MOD_LALT)); | SC_SHORTCUT_MOD_LCTRL));
assert(mods.data[2] == (SC_SHORTCUT_MOD_LCTRL | SC_SHORTCUT_MOD_RCTRL |
SC_SHORTCUT_MOD_RALT));
ok = sc_parse_shortcut_mods("", &mods); ok = sc_parse_shortcut_mods("", &mods);
assert(!ok); assert(!ok);

View file

@ -7,7 +7,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:8.1.3' classpath 'com.android.tools.build:gradle:8.3.0'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files

View file

@ -28,10 +28,17 @@ To disable only the audio playback, see [no playback](video.md#no-playback).
## Audio only ## Audio only
To play audio only, disable the video: To play audio only, disable video and control:
```bash ```bash
scrcpy --no-video scrcpy --no-video --no-control
```
To play audio without a window:
```bash
# --no-video and --no-control are implied by --no-window
scrcpy --no-window
# interrupt with Ctrl+C # interrupt with Ctrl+C
``` ```

View file

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

View file

@ -15,6 +15,31 @@ scrcpy -n # short version
Read [keyboard](keyboard.md) and [mouse](mouse.md). Read [keyboard](keyboard.md) and [mouse](mouse.md).
## Control only
To control the device without mirroring:
```bash
scrcpy --no-video --no-audio
```
By default, mouse mode is switched to UHID if video mirroring is disabled (a
relative mouse mode is required).
To also use a UHID keyboard, set it explicitly:
```bash
scrcpy --no-video --no-audio --keyboard=uhid
scrcpy --no-video --no-audio -K # short version
```
To use AOA instead (over USB only):
```bash
scrcpy --no-video --no-audio --keyboard=aoa --mouse=aoa
```
## Copy-paste ## Copy-paste
Any time the Android clipboard changes, it is automatically synchronized to the Any time the Android clipboard changes, it is automatically synchronized to the
@ -81,15 +106,6 @@ only inverts _x_.
This only works for the default mouse mode (`--mouse=sdk`). 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 ## File drop
### Install APK ### Install APK

View file

@ -6,7 +6,7 @@
Scrcpy is packaged in several distributions and package managers: Scrcpy is packaged in several distributions and package managers:
- Debian/Ubuntu: `apt install scrcpy` - Debian/Ubuntu: ~~`apt install scrcpy`~~ _(obsolete version)_
- Arch Linux: `pacman -S scrcpy` - Arch Linux: `pacman -S scrcpy`
- Fedora: `dnf copr enable zeno/scrcpy && dnf install scrcpy` - Fedora: `dnf copr enable zeno/scrcpy && dnf install scrcpy`
- Gentoo: `emerge scrcpy` - Gentoo: `emerge scrcpy`

View file

@ -18,6 +18,14 @@ Note that on some devices, an additional option must be enabled in developer
options for this mouse mode to work. See options for this mouse mode to work. See
[prerequisites](/README.md#prerequisites). [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 ## Physical mouse simulation
@ -68,3 +76,43 @@ debugging disabled (see [OTG](otg.md)).
Note: On Windows, it may only work in [OTG mode](otg.md), not while mirroring 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 (it is not possible to open a USB device if it is already open by another
process like the _adb daemon_). process like the _adb daemon_).
## Mouse bindings
By default, with SDK mouse, right-click triggers BACK (or POWER on) and
middle-click triggers HOME. In addition, the 4th click triggers APP_SWITCH and
the 5th click expands the notification panel.
In AOA and UHID mouse modes, all clicks are forwarded by default.
The shortcuts can be configured using `--mouse-bind=xxxx` for any mouse mode.
The argument must be exactly 4 characters, one for each secondary click:
```
--mouse-bind=xxxx
^^^^
||||
||| `- 5th click
|| `-- 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 with SDK mouse
scrcpy --mouse-bind=++++ # forward all clicks (default for AOA/UHID)
scrcpy --mouse-bind=++bh # forward right and middle clicks,
# use 4th and 5th for BACK and HOME
```

View file

@ -1,19 +1,21 @@
# OTG # OTG
By default, _scrcpy_ injects input events at the Android API level. As an By default, _scrcpy_ injects input events at the Android API level. As an
alternative, when connected over USB, it is possible to send HID events, so that alternative, it is possible to send HID events, so that scrcpy behaves as if it
scrcpy behaves as if it was a physical keyboard and/or mouse connected to the was a [physical keyboard] and/or a [physical mouse] connected to the Android
Android device. device (see [keyboard](keyboard.md) and [mouse](mouse.md)).
A special mode allows to control the device without mirroring, using AOA [physical keyboard]: keyboard.md#physical-keyboard-simulation
[keyboard](keyboard.md#aoa) and [mouse](mouse.md#aoa). Therefore, it is possible [physical mouse]: physical-keyboard-simulation
to run _scrcpy_ with only physical keyboard and mouse simulation (HID), as if
the computer keyboard and mouse were plugged directly to the device via an OTG
cable.
In this mode, `adb` (USB debugging) is not necessary, and mirroring is disabled. A special mode (OTG) allows to control the device using AOA
[keyboard](keyboard.md#aoa) and [mouse](mouse.md#aoa), without using _adb_ at
all (so USB debugging is not necessary). In this mode, video and audio are
disabled, and `--keyboard=aoa and `--mouse=aoa` are implicitly set.
This is similar to `--keyboard=aoa --mouse=aoa`, but without mirroring. Therefore, it is possible to run _scrcpy_ with only physical keyboard and mouse
simulation, as if the computer keyboard and mouse were plugged directly to the
device via an OTG cable.
To enable OTG mode: To enable OTG mode:
@ -23,7 +25,7 @@ scrcpy --otg
scrcpy --otg -s 0123456789abcdef scrcpy --otg -s 0123456789abcdef
``` ```
It is possible to disable HID keyboard or HID mouse: It is possible to disable keyboard or mouse:
```bash ```bash
scrcpy --otg --keyboard=disabled scrcpy --otg --keyboard=disabled
@ -35,3 +37,22 @@ It only works if the device is connected over USB.
## OTG issues on Windows ## OTG issues on Windows
See [FAQ](/FAQ.md#otg-issues-on-windows). See [FAQ](/FAQ.md#otg-issues-on-windows).
## Control only
Note that the purpose of OTG is to control the device without USB debugging
(adb).
If you want to solely control the device without mirroring while USB debugging
is enabled, then OTG mode is not necessary.
Instead, disable video and audio, and select UHID (or AOA):
```bash
scrcpy --no-video --no-audio --keyboard=uhid --mouse=uhid
scrcpy --no-video --no-audio -KM # short version
scrcpy --no-video --no-audio --keyboard=aoa --mouse=aoa
```
One benefit of UHID is that it also works wirelessly.

View file

@ -58,12 +58,10 @@ orientation](video.md#orientation).
## No playback ## No playback
To disable playback while recording: To disable playback and control while recording:
```bash ```bash
scrcpy --no-playback --record=file.mp4 scrcpy --no-playback --no-control --record=file.mp4
scrcpy -Nr file.mkv
# interrupt recording with Ctrl+C
``` ```
It is also possible to disable video and audio playback separately: It is also possible to disable video and audio playback separately:
@ -73,6 +71,13 @@ It is also possible to disable video and audio playback separately:
scrcpy --record=file.mkv --no-audio-playback scrcpy --record=file.mkv --no-audio-playback
``` ```
To also disable the window:
```bash
scrcpy --no-playback --no-window --record=file.mp4
# interrupt recording with Ctrl+C
```
## Time limit ## Time limit
To limit the recording time: To limit the recording time:

View file

@ -13,8 +13,8 @@ It can be changed using `--shortcut-mod`. Possible keys are `lctrl`, `rctrl`,
# use RCtrl for shortcuts # use RCtrl for shortcuts
scrcpy --shortcut-mod=rctrl scrcpy --shortcut-mod=rctrl
# use either LCtrl+LAlt or LSuper for shortcuts # use either LCtrl or LSuper for shortcuts
scrcpy --shortcut-mod=lctrl+lalt,lsuper scrcpy --shortcut-mod=lctrl,lsuper
``` ```
_<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
@ -28,6 +28,8 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| Rotate display right | <kbd>MOD</kbd>+<kbd></kbd> _(right)_ | Rotate display right | <kbd>MOD</kbd>+<kbd></kbd> _(right)_
| Flip display horizontally | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd></kbd> _(left)_ \| <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd></kbd> _(right)_ | Flip display horizontally | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd></kbd> _(left)_ \| <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd></kbd> _(right)_
| Flip display vertically | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd></kbd> _(up)_ \| <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd></kbd> _(down)_ | Flip display vertically | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd></kbd> _(up)_ \| <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd></kbd> _(down)_
| Pause or re-pause display | <kbd>MOD</kbd>+<kbd>z</kbd>
| Unpause display | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>z</kbd>
| Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd> | Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
| Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_ | Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_ | Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_

View file

@ -1,5 +1,14 @@
# Window # Window
## Disable window
To disable window (may be useful for recording or for playing audio only):
```bash
scrcpy --no-window --record=file.mp4
# Ctrl+C to interrupt
```
## Title ## Title
By default, the window title is the device model. It can be changed: By default, the window title is the device model. It can be changed:

View file

@ -4,14 +4,14 @@
Download the [latest release]: Download the [latest release]:
- [`scrcpy-win64-v2.4.zip`][direct-win64] (64-bit) - [`scrcpy-win64-v2.5.zip`][direct-win64] (64-bit)
<sub>SHA-256: `9dc56f21bfa455352ec0c58b40feaf2fb02d67372910a4235e298ece286ff3a9`</sub> <sub>SHA-256: `345cf04a66a9144281dce72ca4e82adfd2c3092463196e586051df4c69e1507b`</sub>
- [`scrcpy-win32-v2.4.zip`][direct-win32] (32-bit) - [`scrcpy-win32-v2.5.zip`][direct-win32] (32-bit)
<sub>SHA-256: `cf92acc45eef37c6ee2db819f92e420ced3bc50f1348dd57f7d6ca1fc80f6116`</sub> <sub>SHA-256: `d56312a92471565fa4f3a6b94e8eb07717c4c90f2c0f05b03ba444e1001806ec`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest [latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-win64-v2.4.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.5/scrcpy-win64-v2.5.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-win32-v2.4.zip [direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.5/scrcpy-win32-v2.5.zip
and extract it. and extract it.

View file

@ -2,8 +2,8 @@
set -e set -e
BUILDDIR=build-auto BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-server-v2.4 PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.5/scrcpy-server-v2.5
PREBUILT_SERVER_SHA256=93c272b7438605c055e127f7444064ed78fa9ca49f81156777fd201e79ce7ba3 PREBUILT_SERVER_SHA256=1488b1105d6aff534873a26bf610cd2aea06ee867dd7a4d9c6bb2c091396eb15
echo "[scrcpy] Downloading prebuilt server..." echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View file

@ -1,5 +1,5 @@
project('scrcpy', 'c', project('scrcpy', 'c',
version: '2.4', version: '2.5',
meson_version: '>= 0.48', meson_version: '>= 0.48',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',

View file

@ -7,8 +7,8 @@ android {
applicationId "com.genymobile.scrcpy" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 34 targetSdkVersion 34
versionCode 20400 versionCode 20500
versionName "2.4" versionName "2.5"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {

View file

@ -12,7 +12,7 @@
set -e set -e
SCRCPY_DEBUG=false SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=2.4 SCRCPY_VERSION_NAME=2.5
PLATFORM=${ANDROID_PLATFORM:-34} PLATFORM=${ANDROID_PLATFORM:-34}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0}

View file

@ -127,6 +127,10 @@ public class CameraCapture extends SurfaceCapture {
StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
android.util.Size[] sizes = highSpeed ? configs.getHighSpeedVideoSizes() : configs.getOutputSizes(MediaCodec.class); android.util.Size[] sizes = highSpeed ? configs.getHighSpeedVideoSizes() : configs.getOutputSizes(MediaCodec.class);
if (sizes == null) {
return null;
}
Stream<android.util.Size> stream = Arrays.stream(sizes); Stream<android.util.Size> stream = Arrays.stream(sizes);
if (maxSize > 0) { if (maxSize > 0) {
stream = stream.filter(it -> it.getWidth() <= maxSize && it.getHeight() <= maxSize); stream = stream.filter(it -> it.getWidth() <= maxSize && it.getHeight() <= maxSize);

View file

@ -118,12 +118,16 @@ public final class LogUtils {
StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
android.util.Size[] sizes = configs.getOutputSizes(MediaCodec.class); android.util.Size[] sizes = configs.getOutputSizes(MediaCodec.class);
for (android.util.Size size : sizes) { if (sizes == null || sizes.length == 0) {
builder.append("\n - ").append(size.getWidth()).append('x').append(size.getHeight()); builder.append("\n (none)");
} else {
for (android.util.Size size : sizes) {
builder.append("\n - ").append(size.getWidth()).append('x').append(size.getHeight());
}
} }
android.util.Size[] highSpeedSizes = configs.getHighSpeedVideoSizes(); android.util.Size[] highSpeedSizes = configs.getHighSpeedVideoSizes();
if (highSpeedSizes.length > 0) { if (highSpeedSizes != null && highSpeedSizes.length > 0) {
builder.append("\n High speed capture (--camera-high-speed):"); builder.append("\n High speed capture (--camera-high-speed):");
for (android.util.Size size : highSpeedSizes) { for (android.util.Size size : highSpeedSizes) {
Range<Integer>[] highFpsRanges = configs.getHighSpeedVideoFpsRanges(); Range<Integer>[] highFpsRanges = configs.getHighSpeedVideoFpsRanges();

View file

@ -45,18 +45,18 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList
} }
try { try {
display = createDisplay();
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
Ln.d("Display: using SurfaceControl API");
} catch (Exception surfaceControlException) {
Rect videoRect = screenInfo.getVideoSize().toRect(); 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 { try {
virtualDisplay = ServiceManager.getDisplayManager() display = createDisplay();
.createVirtualDisplay("scrcpy", videoRect.width(), videoRect.height(), device.getDisplayId(), surface); setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
Ln.d("Display: using DisplayManager API"); Ln.d("Display: using SurfaceControl API");
} catch (Exception displayManagerException) { } catch (Exception surfaceControlException) {
Ln.e("Could not create display using SurfaceControl", surfaceControlException);
Ln.e("Could not create display using DisplayManager", displayManagerException); Ln.e("Could not create display using DisplayManager", displayManagerException);
Ln.e("Could not create display using SurfaceControl", surfaceControlException);
throw new AssertionError("Could not create display"); throw new AssertionError("Could not create display");
} }
} }
@ -68,6 +68,11 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList
device.setFoldListener(null); device.setFoldListener(null);
if (display != null) { if (display != null) {
SurfaceControl.destroyDisplay(display); SurfaceControl.destroyDisplay(display);
display = null;
}
if (virtualDisplay != null) {
virtualDisplay.release();
virtualDisplay = null;
} }
} }

View file

@ -3,6 +3,7 @@ package com.genymobile.scrcpy;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCodecInfo; import android.media.MediaCodecInfo;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.Build;
import android.os.Looper; import android.os.Looper;
import android.os.SystemClock; import android.os.SystemClock;
import android.view.Surface; import android.view.Surface;
@ -47,7 +48,7 @@ public class SurfaceEncoder implements AsyncProcessor {
this.downsizeOnError = downsizeOnError; this.downsizeOnError = downsizeOnError;
} }
private void streamScreen() throws IOException, ConfigurationException { private void streamCapture() throws IOException, ConfigurationException {
Codec codec = streamer.getCodec(); Codec codec = streamer.getCodec();
MediaCodec mediaCodec = createMediaCodec(codec, encoderName); MediaCodec mediaCodec = createMediaCodec(codec, encoderName);
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions); MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
@ -220,6 +221,9 @@ public class SurfaceEncoder implements AsyncProcessor {
// must be present to configure the encoder, but does not impact the actual frame rate, which is variable // must be present to configure the encoder, but does not impact the actual frame rate, which is variable
format.setInteger(MediaFormat.KEY_FRAME_RATE, 60); format.setInteger(MediaFormat.KEY_FRAME_RATE, 60);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
}
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, DEFAULT_I_FRAME_INTERVAL); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, DEFAULT_I_FRAME_INTERVAL);
// display the very first frame, and recover from bad quality when no new frames // display the very first frame, and recover from bad quality when no new frames
format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, REPEAT_FRAME_DELAY_US); // µs format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, REPEAT_FRAME_DELAY_US); // µs
@ -250,7 +254,7 @@ public class SurfaceEncoder implements AsyncProcessor {
Looper.prepare(); Looper.prepare();
try { try {
streamScreen(); streamCapture();
} catch (ConfigurationException e) { } catch (ConfigurationException e) {
// Do not print stack trace, a user-friendly error-message has already been logged // Do not print stack trace, a user-friendly error-message has already been logged
} catch (IOException e) { } catch (IOException e) {

View file

@ -12,12 +12,15 @@ import java.lang.reflect.Method;
public final class WindowManager { public final class WindowManager {
private final IInterface manager; private final IInterface manager;
private Method getRotationMethod; private Method getRotationMethod;
private Method freezeRotationMethod;
private Method freezeDisplayRotationMethod; private Method freezeDisplayRotationMethod;
private Method isRotationFrozenMethod; private int freezeDisplayRotationMethodVersion;
private Method isDisplayRotationFrozenMethod; private Method isDisplayRotationFrozenMethod;
private Method thawRotationMethod; private int isDisplayRotationFrozenMethodVersion;
private Method thawDisplayRotationMethod; private Method thawDisplayRotationMethod;
private int thawDisplayRotationMethodVersion;
static WindowManager create() { static WindowManager create() {
IInterface manager = ServiceManager.getService("window", "android.view.IWindowManager"); IInterface manager = ServiceManager.getService("window", "android.view.IWindowManager");
@ -43,50 +46,61 @@ public final class WindowManager {
return getRotationMethod; return getRotationMethod;
} }
private Method getFreezeRotationMethod() throws NoSuchMethodException {
if (freezeRotationMethod == null) {
freezeRotationMethod = manager.getClass().getMethod("freezeRotation", int.class);
}
return freezeRotationMethod;
}
// New method added by this commit:
// <https://android.googlesource.com/platform/frameworks/base/+/90c9005e687aa0f63f1ac391adc1e8878ab31759%5E%21/>
private Method getFreezeDisplayRotationMethod() throws NoSuchMethodException { private Method getFreezeDisplayRotationMethod() throws NoSuchMethodException {
if (freezeDisplayRotationMethod == null) { if (freezeDisplayRotationMethod == null) {
freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class); try {
// Android 15 preview and 14 QPR3 Beta added a String caller parameter for debugging:
// <https://android.googlesource.com/platform/frameworks/base/+/670fb7f5c0d23cf51ead25538bcb017e03ed73ac%5E%21/>
freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class, String.class);
freezeDisplayRotationMethodVersion = 0;
} catch (NoSuchMethodException e) {
try {
// New method added by this commit:
// <https://android.googlesource.com/platform/frameworks/base/+/90c9005e687aa0f63f1ac391adc1e8878ab31759%5E%21/>
freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class);
freezeDisplayRotationMethodVersion = 1;
} catch (NoSuchMethodException e1) {
freezeDisplayRotationMethod = manager.getClass().getMethod("freezeRotation", int.class);
freezeDisplayRotationMethodVersion = 2;
}
}
} }
return freezeDisplayRotationMethod; return freezeDisplayRotationMethod;
} }
private Method getIsRotationFrozenMethod() throws NoSuchMethodException {
if (isRotationFrozenMethod == null) {
isRotationFrozenMethod = manager.getClass().getMethod("isRotationFrozen");
}
return isRotationFrozenMethod;
}
// New method added by this commit:
// <https://android.googlesource.com/platform/frameworks/base/+/90c9005e687aa0f63f1ac391adc1e8878ab31759%5E%21/>
private Method getIsDisplayRotationFrozenMethod() throws NoSuchMethodException { private Method getIsDisplayRotationFrozenMethod() throws NoSuchMethodException {
if (isDisplayRotationFrozenMethod == null) { if (isDisplayRotationFrozenMethod == null) {
isDisplayRotationFrozenMethod = manager.getClass().getMethod("isDisplayRotationFrozen", int.class); try {
// New method added by this commit:
// <https://android.googlesource.com/platform/frameworks/base/+/90c9005e687aa0f63f1ac391adc1e8878ab31759%5E%21/>
isDisplayRotationFrozenMethod = manager.getClass().getMethod("isDisplayRotationFrozen", int.class);
isDisplayRotationFrozenMethodVersion = 0;
} catch (NoSuchMethodException e) {
isDisplayRotationFrozenMethod = manager.getClass().getMethod("isRotationFrozen");
isDisplayRotationFrozenMethodVersion = 1;
}
} }
return isDisplayRotationFrozenMethod; return isDisplayRotationFrozenMethod;
} }
private Method getThawRotationMethod() throws NoSuchMethodException {
if (thawRotationMethod == null) {
thawRotationMethod = manager.getClass().getMethod("thawRotation");
}
return thawRotationMethod;
}
// New method added by this commit:
// <https://android.googlesource.com/platform/frameworks/base/+/90c9005e687aa0f63f1ac391adc1e8878ab31759%5E%21/>
private Method getThawDisplayRotationMethod() throws NoSuchMethodException { private Method getThawDisplayRotationMethod() throws NoSuchMethodException {
if (thawDisplayRotationMethod == null) { if (thawDisplayRotationMethod == null) {
thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class); try {
// Android 15 preview and 14 QPR3 Beta added a String caller parameter for debugging:
// <https://android.googlesource.com/platform/frameworks/base/+/670fb7f5c0d23cf51ead25538bcb017e03ed73ac%5E%21/>
thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class, String.class);
thawDisplayRotationMethodVersion = 0;
} catch (NoSuchMethodException e) {
try {
// New method added by this commit:
// <https://android.googlesource.com/platform/frameworks/base/+/90c9005e687aa0f63f1ac391adc1e8878ab31759%5E%21/>
thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class);
thawDisplayRotationMethodVersion = 1;
} catch (NoSuchMethodException e1) {
thawDisplayRotationMethod = manager.getClass().getMethod("thawRotation");
thawDisplayRotationMethodVersion = 2;
}
}
} }
return thawDisplayRotationMethod; return thawDisplayRotationMethod;
} }
@ -103,16 +117,21 @@ public final class WindowManager {
public void freezeRotation(int displayId, int rotation) { public void freezeRotation(int displayId, int rotation) {
try { try {
try { Method method = getFreezeDisplayRotationMethod();
Method method = getFreezeDisplayRotationMethod(); switch (freezeDisplayRotationMethodVersion) {
method.invoke(manager, displayId, rotation); case 0:
} catch (ReflectiveOperationException e) { method.invoke(manager, displayId, rotation, "scrcpy#freezeRotation");
if (displayId == 0) { break;
Method method = getFreezeRotationMethod(); case 1:
method.invoke(manager, displayId, rotation);
break;
default:
if (displayId != 0) {
Ln.e("Secondary display rotation not supported on this device");
return;
}
method.invoke(manager, rotation); method.invoke(manager, rotation);
} else { break;
Ln.e("Could not invoke method", e);
}
} }
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
Ln.e("Could not invoke method", e); Ln.e("Could not invoke method", e);
@ -121,17 +140,16 @@ public final class WindowManager {
public boolean isRotationFrozen(int displayId) { public boolean isRotationFrozen(int displayId) {
try { try {
try { Method method = getIsDisplayRotationFrozenMethod();
Method method = getIsDisplayRotationFrozenMethod(); switch (isDisplayRotationFrozenMethodVersion) {
return (boolean) method.invoke(manager, displayId); case 0:
} catch (ReflectiveOperationException e) { return (boolean) method.invoke(manager, displayId);
if (displayId == 0) { default:
Method method = getIsRotationFrozenMethod(); if (displayId != 0) {
Ln.e("Secondary display rotation not supported on this device");
return false;
}
return (boolean) method.invoke(manager); return (boolean) method.invoke(manager);
} else {
Ln.e("Could not invoke method", e);
return false;
}
} }
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
Ln.e("Could not invoke method", e); Ln.e("Could not invoke method", e);
@ -141,16 +159,21 @@ public final class WindowManager {
public void thawRotation(int displayId) { public void thawRotation(int displayId) {
try { try {
try { Method method = getThawDisplayRotationMethod();
Method method = getThawDisplayRotationMethod(); switch (thawDisplayRotationMethodVersion) {
method.invoke(manager, displayId); case 0:
} catch (ReflectiveOperationException e) { method.invoke(manager, displayId, "scrcpy#thawRotation");
if (displayId == 0) { break;
Method method = getThawRotationMethod(); case 1:
method.invoke(manager, displayId);
break;
default:
if (displayId != 0) {
Ln.e("Secondary display rotation not supported on this device");
return;
}
method.invoke(manager); method.invoke(manager);
} else { break;
Ln.e("Could not invoke method", e);
}
} }
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
Ln.e("Could not invoke method", e); Ln.e("Could not invoke method", e);
@ -166,6 +189,10 @@ public final class WindowManager {
cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, displayId); cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, displayId);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
// old version // old version
if (displayId != 0) {
Ln.e("Secondary display rotation not supported on this device");
return;
}
cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher); cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher);
} }
} catch (Exception e) { } catch (Exception e) {