Compare commits

..

26 Commits

Author SHA1 Message Date
Romain Vimont
52fa2c97ca Document UHID
Rework the documentation to present the keyboard and mouse input modes.
2024-03-01 00:12:10 +01:00
Romain Vimont
d196f0662c Check options specific to SDK keyboard
Fail if an option specific to --keyboard=sdk is passed with another
keyboard input mode.
2024-03-01 00:12:10 +01:00
Romain Vimont
0f2a183f4f Do not fallback keyboard mode if AOA fails
Initially, if AOA initialization failed, default injection method was
used, in order to use the same command/shortcut when the device is
connected via USB or via TCP/IP, without changing the arguments.

Now that there are 3 keyboard modes, it seems unexpected to switch to
another specific mode if AOA fails (and it is inconsistent). If the user
explicitly requests AOA, then use AOA or fail.

Refs #2632 comment <https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-945190859>
2024-03-01 00:12:10 +01:00
Romain Vimont
0edc3164e2 Reassign -K and -M to UHID keyboard and mouse
The options were deprecated, but for convenience, reassign them to
aliases for --keyboard=uhid and --mouse=uhid respectively.

Their long version (--hid-keyboard and --hid-mouse) remain deprecated.
2024-03-01 00:12:10 +01:00
Romain Vimont
a779a9f112 Add UHID mouse support
Use the following command:

    scrcpy --mouse=uhid
2024-03-01 00:12:10 +01:00
Romain Vimont
8a121cdf59 Add shortcut to open keyboard settings
The keyboard settings can be opened by:

    adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS

Add a shortcut (MOD+k) for convenience if the current keyboard is HID.
2024-03-01 00:12:10 +01:00
Romain Vimont
58fba407b3 Fix startActivity() for supporting API < 30
Call the older startActivityAsUser() instead of
startActivityAsUserWithFeature() so that it also works on older Android
versions.

Fixes #4704 <https://github.com/Genymobile/scrcpy/issues/4704>
2024-03-01 00:12:10 +01:00
Romain Vimont
9be51bc129 Create UhidManager only on first use
There is no need to create a UhidManager instance (with its thread) if
no UHID is used.
2024-03-01 00:12:10 +01:00
Simon Chan
7d8cfa5be0 Handle UHID output
Use UHID output reports to synchronize CapsLock and VerrNum states.

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-03-01 00:12:10 +01:00
Romain Vimont
c713868005 Refactor DeviceMessageSender
Refactor DeviceMessage as a queue of message. This will allow to add
other message types.
2024-03-01 00:12:10 +01:00
Simon Chan
0b9fa9d760 Add UHID keyboard support
Use the following command:

    scrcpy --keyboard=uhid

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-03-01 00:12:10 +01:00
Romain Vimont
185d6263cf Log controller handling errors
On close, the controller is expected to throw an IOException because the
socket is closed, so the exception was ignored.

However, message handling actions may also throw IOException, and they
must not be silently ignored.
2024-03-01 00:12:10 +01:00
Romain Vimont
650a556e1c Initialize controller before keyboards
The UHID keyboard initializer will need the controller.
2024-03-01 00:12:10 +01:00
Romain Vimont
8ea89fdb5c Initialize controller in two steps
There is a dependency cycle in the initialization order:
 - keyboard depends on controller
 - controller depends on acksync
 - acksync depends on keyboard initialization

To break this cycle, bind the async instance to the controller in a
second step.
2024-03-01 00:12:10 +01:00
Romain Vimont
857eb419a9 Extract binary to hex string conversion 2024-03-01 00:12:10 +01:00
Romain Vimont
7a6a0e58d5 Rename default keyboard implementation to "sdk"
Rename {keyboard,mouse}_inject to {keyboard,mouse}_sdk.

All implementations "inject" key events and mouse events, what differs
is the mechanism. For these implementations, the Android SDK API is used
to inject events.

Note that the input mode enum variants were already renamed
(SC_KEYBOARD_INPUT_MODE_SDK and SC_MOUSE_INPUT_MODE_SDK).
2024-03-01 00:12:10 +01:00
Romain Vimont
3057da14b7 Extract mouse HID handling
Split the mouse implementation using AOA and the code handling HID
events, so that HID events can be reused for another protocol (UHID).
2024-03-01 00:12:10 +01:00
Romain Vimont
a1419bccf7 Extract keyboard HID handling
Split the keyboard implementation using AOA and the code handling HID
events, so that HID events can be reused for another protocol (UHID).
2024-03-01 00:12:10 +01:00
Romain Vimont
b86f327304 Extract HID events struct
An event contained several fields:
 - the accessory id
 - the HID event data
 - a field ack_to_wait specific to the AOA implementation.

Extract the HID event part to prepare the factorization of HID event
creation.
2024-03-01 00:12:10 +01:00
Romain Vimont
96a3154383 Embed HID event data
In the implementation, an HID event is at most 8 bytes. Embed the data
in the HID event structure to avoid allocations and simplify the code.
2024-03-01 00:12:10 +01:00
Romain Vimont
f845f96034 Rename hid event "buffer" to "data"
This fields contains the HID event data (there is no "bufferization").
2024-03-01 00:12:10 +01:00
Romain Vimont
af3888f502 Rename "buffer" to "data"
The variable name is intended to match the parameter name of
libusb_control_transfer().
2024-03-01 00:12:10 +01:00
Romain Vimont
7dc681b4a5 Fix HID mouse documentation
The size of a mouse HID event is 4 bytes.
2024-03-01 00:12:10 +01:00
Simon Chan
37315ff1c1 Introduce --keyboard and --mouse
Until now, there was two modes for keyboard and mouse:
 - event injection using the Android system API (default)
 - HID/AOA over USB

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

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

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

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

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

This will allow to add assertions when keyboard and mouse could be
disabled separately.
2024-03-01 00:12:10 +01:00
54 changed files with 414 additions and 1104 deletions

View File

@@ -188,7 +188,7 @@
identification within third-party archives.
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2024 Romain Vimont
Copyright (C) 2018-2023 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -1,8 +1,4 @@
**This GitHub repo (<https://github.com/Genymobile/scrcpy>) is the only official
source for the project. Do not download releases from random websites, even if
their name contains `scrcpy`.**
# scrcpy (v2.4)
# scrcpy (v2.3.1)
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
@@ -70,41 +66,6 @@ Note that USB debugging is not required to run scrcpy in [OTG mode](doc/otg.md).
- [macOS](doc/macos.md)
## Usage examples
There are a lot of options, [documented](#user-documentation) in separate pages.
Here are just some common examples.
- Capture the screen in H.265 (better quality), limit the size to 1920, limit
the frame rate to 60fps, disable audio, and control the device by simulating
a physical keyboard:
```bash
scrcpy --video-codec=h265 --max-size=1920 --max-fps=60 --no-audio --keyboard=uhid
scrcpy --video-codec=h265 -m1920 --max-fps=60 --no-audio -K # short version
```
- Record the device camera in H.265 at 1920x1080 (and microphone) to an MP4
file:
```bash
scrcpy --video-source=camera --video-codec=h265 --camera-size=1920x1080 --record=file.mp4
```
- Capture the device front camera and expose it as a webcam on the computer (on
Linux):
```bash
scrcpy --video-source=camera --camera-size=1920x1080 --camera-facing=front --v4l2-sink=/dev/video2 --no-playback
```
- Control the device without mirroring by simulating a physical keyboard and
mouse (USB debugging not required):
```bash
scrcpy --otg
```
## User documentation
The application provides a lot of features and configuration options. They are
@@ -173,7 +134,7 @@ work][donate]:
## Licence
Copyright (C) 2018 Genymobile
Copyright (C) 2018-2024 Romain Vimont
Copyright (C) 2018-2023 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

1
app/deps/.gitignore vendored
View File

@@ -1 +0,0 @@
/work

View File

@@ -1,27 +0,0 @@
This directory (app/deps/) contains:
*.sh : shell scripts to download and build dependencies
patches/ : patches to fix dependencies (used by scripts)
work/sources/ : downloaded tarballs and extracted folders
ffmpeg-6.1.1.tar.xz
ffmpeg-6.1.1/
libusb-1.0.27.tar.gz
libusb-1.0.27/
...
work/build/ : build dirs for each dependency/version/architecture
ffmpeg-6.1.1/win32/
ffmpeg-6.1.1/win64/
libusb-1.0.27/win32/
libusb-1.0.27/win64/
...
work/install/ : install dirs for each architexture
win32/bin/
win32/include/
win32/lib/
win32/share/
win64/bin/
win64/include/
win64/lib/
win64/share/

View File

@@ -1,32 +0,0 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
VERSION=34.0.5
FILENAME=platform-tools_r$VERSION-windows.zip
PROJECT_DIR=platform-tools-$VERSION
SHA256SUM=3f8320152704377de150418a3c4c9d07d16d80a6c0d0d8f7289c22c499e33571
cd "$SOURCES_DIR"
if [[ -d "$PROJECT_DIR" ]]
then
echo "$PWD/$PROJECT_DIR" found
else
get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM"
mkdir -p "$PROJECT_DIR"
cd "$PROJECT_DIR"
ZIP_PREFIX=platform-tools
unzip "../$FILENAME" \
"$ZIP_PREFIX"/AdbWinApi.dll \
"$ZIP_PREFIX"/AdbWinUsbApi.dll \
"$ZIP_PREFIX"/adb.exe
mv "$ZIP_PREFIX"/* .
rmdir "$ZIP_PREFIX"
fi
mkdir -p "$INSTALL_DIR/$HOST/bin"
cd "$INSTALL_DIR/$HOST/bin"
cp -r "$SOURCES_DIR/$PROJECT_DIR"/. "$INSTALL_DIR/$HOST/bin/"

View File

@@ -1,55 +0,0 @@
#!/usr/bin/env bash
# This file is intended to be sourced by other scripts, not executed
if [[ $# != 1 ]]
then
# <host>: win32 or win64
echo "Syntax: $0 <host>" >&2
exit 1
fi
HOST="$1"
if [[ "$HOST" = win32 ]]
then
HOST_TRIPLET=i686-w64-mingw32
elif [[ "$HOST" = win64 ]]
then
HOST_TRIPLET=x86_64-w64-mingw32
else
echo "Unsupported host: $HOST" >&2
exit 1
fi
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
PATCHES_DIR="$PWD/patches"
WORK_DIR="$PWD/work"
SOURCES_DIR="$WORK_DIR/sources"
BUILD_DIR="$WORK_DIR/build"
INSTALL_DIR="$WORK_DIR/install"
mkdir -p "$INSTALL_DIR" "$SOURCES_DIR" "$WORK_DIR"
checksum() {
local file="$1"
local sum="$2"
echo "$file: verifying checksum..."
echo "$sum $file" | sha256sum -c
}
get_file() {
local url="$1"
local file="$2"
local sum="$3"
if [[ -f "$file" ]]
then
echo "$file: found"
else
echo "$file: not found, downloading..."
wget "$url" -O "$file"
fi
checksum "$file" "$sum"
}

View File

@@ -1,91 +0,0 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
VERSION=6.1.1
FILENAME=ffmpeg-$VERSION.tar.xz
PROJECT_DIR=ffmpeg-$VERSION
SHA256SUM=8684f4b00f94b85461884c3719382f1261f0d9eb3d59640a1f4ac0873616f968
cd "$SOURCES_DIR"
if [[ -d "$PROJECT_DIR" ]]
then
echo "$PWD/$PROJECT_DIR" found
else
get_file "https://ffmpeg.org/releases/$FILENAME" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
patch -d "$PROJECT_DIR" -p1 < "$PATCHES_DIR"/ffmpeg-6.1-fix-build.patch
fi
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
cd "$BUILD_DIR/$PROJECT_DIR"
if [[ "$HOST" = win32 ]]
then
ARCH=x86
elif [[ "$HOST" = win64 ]]
then
ARCH=x86_64
else
echo "Unsupported host: $HOST" >&2
exit 1
fi
# -static-libgcc to avoid missing libgcc_s_dw2-1.dll
# -static to avoid dynamic dependency to zlib
export CFLAGS='-static-libgcc -static'
export CXXFLAGS="$CFLAGS"
export LDFLAGS='-static-libgcc -static'
if [[ -d "$HOST" ]]
then
echo "'$PWD/$HOST' already exists, not reconfigured"
cd "$HOST"
else
mkdir "$HOST"
cd "$HOST"
"$SOURCES_DIR/$PROJECT_DIR"/configure \
--prefix="$INSTALL_DIR/$HOST" \
--enable-cross-compile \
--target-os=mingw32 \
--arch="$ARCH" \
--cross-prefix="${HOST_TRIPLET}-" \
--cc="${HOST_TRIPLET}-gcc" \
--extra-cflags="-O2 -fPIC" \
--enable-shared \
--disable-static \
--disable-programs \
--disable-doc \
--disable-swscale \
--disable-postproc \
--disable-avfilter \
--disable-avdevice \
--disable-network \
--disable-everything \
--enable-swresample \
--enable-decoder=h264 \
--enable-decoder=hevc \
--enable-decoder=av1 \
--enable-decoder=pcm_s16le \
--enable-decoder=opus \
--enable-decoder=aac \
--enable-decoder=flac \
--enable-decoder=png \
--enable-protocol=file \
--enable-demuxer=image2 \
--enable-parser=png \
--enable-zlib \
--enable-muxer=matroska \
--enable-muxer=mp4 \
--enable-muxer=opus \
--enable-muxer=flac \
--enable-muxer=wav \
--disable-vulkan
fi
make -j
make install

View File

@@ -1,45 +0,0 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
VERSION=1.0.27
FILENAME=libusb-$VERSION.tar.gz
PROJECT_DIR=libusb-$VERSION
SHA256SUM=e8f18a7a36ecbb11fb820bd71540350d8f61bcd9db0d2e8c18a6fb80b214a3de
cd "$SOURCES_DIR"
if [[ -d "$PROJECT_DIR" ]]
then
echo "$PWD/$PROJECT_DIR" found
else
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"
fi
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
cd "$BUILD_DIR/$PROJECT_DIR"
export CFLAGS='-O2'
export CXXFLAGS="$CFLAGS"
if [[ -d "$HOST" ]]
then
echo "'$PWD/$HOST' already exists, not reconfigured"
cd "$HOST"
else
mkdir "$HOST"
cd "$HOST"
"$SOURCES_DIR/$PROJECT_DIR"/bootstrap.sh
"$SOURCES_DIR/$PROJECT_DIR"/configure \
--prefix="$INSTALL_DIR/$HOST" \
--host="$HOST_TRIPLET" \
--enable-shared \
--disable-static
fi
make -j
make install-strip

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

@@ -1,47 +0,0 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
VERSION=2.28.5
FILENAME=SDL-$VERSION.tar.gz
PROJECT_DIR=SDL-release-$VERSION
SHA256SUM=9f0556e4a24ef5b267010038ad9e9948b62f236d5bcc4b22179f95ef62d84023
cd "$SOURCES_DIR"
if [[ -d "$PROJECT_DIR" ]]
then
echo "$PWD/$PROJECT_DIR" found
else
get_file "https://github.com/libsdl-org/SDL/archive/refs/tags/release-$VERSION.tar.gz" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
fi
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
cd "$BUILD_DIR/$PROJECT_DIR"
export CFLAGS='-O2'
export CXXFLAGS="$CFLAGS"
if [[ -d "$HOST" ]]
then
echo "'$PWD/$HOST' already exists, not reconfigured"
cd "$HOST"
else
mkdir "$HOST"
cd "$HOST"
"$SOURCES_DIR/$PROJECT_DIR"/configure \
--prefix="$INSTALL_DIR/$HOST" \
--host="$HOST_TRIPLET" \
--enable-shared \
--disable-static
fi
make -j
# There is no "make install-strip"
make install
# Strip manually
${HOST_TRIPLET}-strip "$INSTALL_DIR/$HOST/bin/SDL2.dll"

1
app/prebuilt-deps/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/data

22
app/prebuilt-deps/common Executable file
View File

@@ -0,0 +1,22 @@
PREBUILT_DATA_DIR=data
checksum() {
local file="$1"
local sum="$2"
echo "$file: verifying checksum..."
echo "$sum $file" | sha256sum -c
}
get_file() {
local url="$1"
local file="$2"
local sum="$3"
if [[ -f "$file" ]]
then
echo "$file: found"
else
echo "$file: not found, downloading..."
wget "$url" -O "$file"
fi
checksum "$file" "$sum"
}

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=platform-tools-34.0.5
FILENAME=platform-tools_r34.0.5-windows.zip
SHA256SUM=3f8320152704377de150418a3c4c9d07d16d80a6c0d0d8f7289c22c499e33571
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://dl.google.com/android/repository/$FILENAME" \
"$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
ZIP_PREFIX=platform-tools
unzip "../$FILENAME" \
"$ZIP_PREFIX"/AdbWinApi.dll \
"$ZIP_PREFIX"/AdbWinUsbApi.dll \
"$ZIP_PREFIX"/adb.exe
mv "$ZIP_PREFIX"/* .
rmdir "$ZIP_PREFIX"

View File

@@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
VERSION=6.1-scrcpy-3
DEP_DIR="ffmpeg-$VERSION"
FILENAME="$DEP_DIR".7z
SHA256SUM=b646d18a3d543a4e4c46881568213499f22e4454a464e1552f03f2ac9cc3a05a
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://github.com/rom1v/scrcpy-deps/releases/download/$VERSION/$FILENAME" \
"$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
ZIP_PREFIX=ffmpeg
7z x "../$FILENAME"
mv "$ZIP_PREFIX"/* .
rmdir "$ZIP_PREFIX"

View File

@@ -0,0 +1,37 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
VERSION=1.0.26
DEP_DIR="libusb-$VERSION"
FILENAME="libusb-$VERSION-binaries.7z"
SHA256SUM=9c242696342dbde9cdc47239391f71833939bf9f7aa2bbb28cdaabe890465ec5
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://github.com/libusb/libusb/releases/download/v$VERSION/$FILENAME" \
"$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
7z x "../$FILENAME" \
"libusb-$VERSION-binaries/libusb-MinGW-Win32/" \
"libusb-$VERSION-binaries/libusb-MinGW-x64/"
mv "libusb-$VERSION-binaries/libusb-MinGW-Win32" .
mv "libusb-$VERSION-binaries/libusb-MinGW-x64" .
rm -rf "libusb-$VERSION-binaries"
# Rename the dll to get the same library name on all platforms
mv libusb-MinGW-Win32/bin/msys-usb-1.0.dll libusb-MinGW-Win32/bin/libusb-1.0.dll
mv libusb-MinGW-x64/bin/msys-usb-1.0.dll libusb-MinGW-x64/bin/libusb-1.0.dll

View File

@@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
VERSION=2.28.5
DEP_DIR="SDL2-$VERSION"
FILENAME="SDL2-devel-$VERSION-mingw.tar.gz"
SHA256SUM=3c0c655c2ebf67cad48fead72761d1601740ded30808952c3274ba223d226c21
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://github.com/libsdl-org/SDL/releases/download/release-$VERSION/$FILENAME" \
"$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
TAR_PREFIX="$DEP_DIR" # root directory inside the tar has the same name
tar xf "../$FILENAME" --strip-components=1 \
"$TAR_PREFIX"/i686-w64-mingw32/bin/SDL2.dll \
"$TAR_PREFIX"/i686-w64-mingw32/include/ \
"$TAR_PREFIX"/i686-w64-mingw32/lib/ \
"$TAR_PREFIX"/x86_64-w64-mingw32/bin/SDL2.dll \
"$TAR_PREFIX"/x86_64-w64-mingw32/include/ \
"$TAR_PREFIX"/x86_64-w64-mingw32/lib/ \

View File

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

View File

@@ -316,10 +316,6 @@ Disable video forwarding.
.B \-\-no\-video\-playback
Disable video playback on the computer.
.TP
.B \-\-no\-window
Disable scrcpy window. Implies --no-video-playback and --no-control.
.TP
.BI "\-\-orientation " value
Same as --display-orientation=value --record-orientation=value.
@@ -581,14 +577,6 @@ Flip display horizontally
.B MOD+Shift+Up, MOD+Shift+Down
Flip display vertically
.TP
.B MOD+z
Pause or re-pause display
.TP
.B MOD+Shift+z
Unpause display
.TP
.B MOD+g
Resize window to 1:1 (pixel\-perfect)
@@ -726,7 +714,7 @@ Report bugs to <https://github.com/Genymobile/scrcpy/issues>.
.SH COPYRIGHT
Copyright \(co 2018 Genymobile <https://www.genymobile.com>
Copyright \(co 2018\-2024 Romain Vimont <rom@rom1v.com>
Copyright \(co 2018\-2023 Romain Vimont <rom@rom1v.com>
Licensed under the Apache License, Version 2.0.

View File

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

View File

@@ -97,7 +97,6 @@ enum {
OPT_MOUSE,
OPT_HID_KEYBOARD_DEPRECATED,
OPT_HID_MOUSE_DEPRECATED,
OPT_NO_WINDOW,
};
struct sc_option {
@@ -567,12 +566,6 @@ static const struct sc_option options[] = {
.longopt = "no-video-playback",
.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 = "orientation",
@@ -907,14 +900,6 @@ static const struct sc_shortcut shortcuts[] = {
.shortcuts = { "MOD+Shift+Up", "MOD+Shift+Down" },
.text = "Flip display vertically",
},
{
.shortcuts = { "MOD+z" },
.text = "Pause or re-pause display",
},
{
.shortcuts = { "MOD+Shift+z" },
.text = "Unpause display",
},
{
.shortcuts = { "MOD+g" },
.text = "Resize window to 1:1 (pixel-perfect)",
@@ -2493,9 +2478,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_CAMERA_HIGH_SPEED:
opts->camera_high_speed = true;
break;
case OPT_NO_WINDOW:
opts->window = false;
break;
default:
// getopt prints the error message on stderr
return false;
@@ -2533,12 +2515,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
v4l2 = !!opts->v4l2_device;
#endif
if (!opts->window) {
// Without window, there cannot be any video playback or control
opts->video_playback = false;
opts->control = false;
}
if (!opts->video) {
opts->video_playback = false;
// Do not power on the device on start if video capture is disabled
@@ -2560,8 +2536,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->audio = false;
}
if (!opts->video && !opts->audio && !opts->control && !otg) {
LOGE("No video, no audio, no control, no OTG: nothing to do");
if (!opts->video && !opts->audio && !otg) {
LOGE("No video, no audio, no OTG: nothing to do");
return false;
}
@@ -2572,9 +2548,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_codec == SC_CODEC_FLAC) {
// Use 50 ms audio buffer by default, but use a higher value for
// FLAC, which is not low latency (the default encoder produces
// blocks of 4096 samples, which represent ~85.333ms).
// Use 50 ms audio buffer by default, but use a higher value for FLAC,
// which is not low latency (the default encoder produces blocks of
// 4096 samples, which represent ~85.333ms).
LOGI("FLAC audio: audio buffer increased to 120 ms (use "
"--audio-buffer to set a custom value)");
opts->audio_buffer = SC_TICK_FROM_MS(120);
@@ -2585,11 +2561,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
#ifdef HAVE_V4L2
if (v4l2) {
if (!opts->video) {
LOGE("V4L2 sink requires video capture, but --no-video was set.");
return false;
}
if (opts->lock_video_orientation ==
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
LOGI("Video orientation is locked for v4l2 sink. "
@@ -2609,33 +2580,16 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
#endif
if (opts->control) {
if (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) {
opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA
: SC_KEYBOARD_INPUT_MODE_SDK;
}
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) {
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->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) {
opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA
: SC_KEYBOARD_INPUT_MODE_SDK;
}
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) {
opts->mouse_input_mode = otg ? SC_MOUSE_INPUT_MODE_AOA
: SC_MOUSE_INPUT_MODE_SDK;
}
if (otg) {
if (!opts->control) {
LOGE("--no-control is not allowed in OTG mode");
return false;
}
enum sc_keyboard_input_mode kmode = opts->keyboard_input_mode;
if (kmode != SC_KEYBOARD_INPUT_MODE_AOA
&& kmode != SC_KEYBOARD_INPUT_MODE_DISABLED) {
@@ -2738,11 +2692,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
if (opts->record_filename) {
if (!opts->video && !opts->audio) {
LOGE("Video and audio disabled, nothing to record");
return false;
}
if (!opts->record_format) {
opts->record_format = guess_record_format(opts->record_filename);
if (!opts->record_format) {
@@ -2849,11 +2798,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
# 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) {
// OTG mode is compatible with only very few options.
// Only report obvious errors.

View File

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

View File

@@ -22,19 +22,10 @@ struct sc_controller {
bool stopped;
struct sc_control_msg_queue queue;
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
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
const struct sc_controller_callbacks *cbs,
void *cbs_userdata);
sc_controller_init(struct sc_controller *controller, sc_socket control_socket);
void
sc_controller_configure(struct sc_controller *controller,

View File

@@ -1,34 +1,11 @@
#include "display.h"
#include <assert.h>
#include <libavutil/pixfmt.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
sc_display_init(struct sc_display *display, SDL_Window *window,
SDL_Surface *icon_novideo, bool mipmaps) {
sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) {
display->renderer =
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!display->renderer) {
@@ -85,22 +62,8 @@ sc_display_init(struct sc_display *display, SDL_Window *window,
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
}
display->texture = NULL;
display->pending.flags = 0;
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;
}
@@ -232,25 +195,9 @@ sc_display_set_texture_size(struct sc_display *display, struct sc_size size) {
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
sc_display_update_texture_internal(struct sc_display *display,
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,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],

View File

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

View File

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

View File

@@ -78,10 +78,7 @@ decode_image(const char *path) {
goto close_input;
}
const AVCodec *codec;
int stream =
av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
int stream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (stream < 0 ) {
LOGE("Could not find best image stream");
goto close_input;
@@ -89,6 +86,12 @@ decode_image(const char *path) {
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);
if (!codec_ctx) {
LOG_OOM();

View File

@@ -402,8 +402,6 @@ sc_input_manager_process_key(struct sc_input_manager *im,
const SDL_KeyboardEvent *event) {
// controller is NULL if --no-control is requested
bool control = im->controller;
bool paused = im->screen->paused;
bool video = im->screen->video;
SDL_Keycode keycode = event->keysym.sym;
uint16_t mod = event->keysym.mod;
@@ -429,68 +427,63 @@ sc_input_manager_process_key(struct sc_input_manager *im,
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
switch (keycode) {
case SDLK_h:
if (im->kp && !shift && !repeat && !paused) {
if (im->kp && !shift && !repeat) {
action_home(im, action);
}
return;
case SDLK_b: // fall-through
case SDLK_BACKSPACE:
if (im->kp && !shift && !repeat && !paused) {
if (im->kp && !shift && !repeat) {
action_back(im, action);
}
return;
case SDLK_s:
if (im->kp && !shift && !repeat && !paused) {
if (im->kp && !shift && !repeat) {
action_app_switch(im, action);
}
return;
case SDLK_m:
if (im->kp && !shift && !repeat && !paused) {
if (im->kp && !shift && !repeat) {
action_menu(im, action);
}
return;
case SDLK_p:
if (im->kp && !shift && !repeat && !paused) {
if (im->kp && !shift && !repeat) {
action_power(im, action);
}
return;
case SDLK_o:
if (control && !repeat && down && !paused) {
if (control && !repeat && down) {
enum sc_screen_power_mode mode = shift
? SC_SCREEN_POWER_MODE_NORMAL
: SC_SCREEN_POWER_MODE_OFF;
set_screen_power_mode(im, mode);
}
return;
case SDLK_z:
if (video && down && !repeat) {
sc_screen_set_paused(im->screen, !shift);
}
return;
case SDLK_DOWN:
if (shift) {
if (video && !repeat && down) {
if (!repeat & down) {
apply_orientation_transform(im,
SC_ORIENTATION_FLIP_180);
}
} else if (im->kp && !paused) {
} else if (im->kp) {
// forward repeated events
action_volume_down(im, action);
}
return;
case SDLK_UP:
if (shift) {
if (video && !repeat && down) {
if (!repeat & down) {
apply_orientation_transform(im,
SC_ORIENTATION_FLIP_180);
}
} else if (im->kp && !paused) {
} else if (im->kp) {
// forward repeated events
action_volume_up(im, action);
}
return;
case SDLK_LEFT:
if (video && !repeat && down) {
if (!repeat && down) {
if (shift) {
apply_orientation_transform(im,
SC_ORIENTATION_FLIP_0);
@@ -501,7 +494,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
}
return;
case SDLK_RIGHT:
if (video && !repeat && down) {
if (!repeat && down) {
if (shift) {
apply_orientation_transform(im,
SC_ORIENTATION_FLIP_0);
@@ -512,17 +505,17 @@ sc_input_manager_process_key(struct sc_input_manager *im,
}
return;
case SDLK_c:
if (im->kp && !shift && !repeat && down && !paused) {
if (im->kp && !shift && !repeat && down) {
get_device_clipboard(im, SC_COPY_KEY_COPY);
}
return;
case SDLK_x:
if (im->kp && !shift && !repeat && down && !paused) {
if (im->kp && !shift && !repeat && down) {
get_device_clipboard(im, SC_COPY_KEY_CUT);
}
return;
case SDLK_v:
if (im->kp && !repeat && down && !paused) {
if (im->kp && !repeat && down) {
if (shift || im->legacy_paste) {
// inject the text as input events
clipboard_paste(im);
@@ -534,27 +527,27 @@ sc_input_manager_process_key(struct sc_input_manager *im,
}
return;
case SDLK_f:
if (video && !shift && !repeat && down) {
if (!shift && !repeat && down) {
sc_screen_switch_fullscreen(im->screen);
}
return;
case SDLK_w:
if (video && !shift && !repeat && down) {
if (!shift && !repeat && down) {
sc_screen_resize_to_fit(im->screen);
}
return;
case SDLK_g:
if (video && !shift && !repeat && down) {
if (!shift && !repeat && down) {
sc_screen_resize_to_pixel_perfect(im->screen);
}
return;
case SDLK_i:
if (video && !shift && !repeat && down) {
if (!shift && !repeat && down) {
switch_fps_counter_state(im);
}
return;
case SDLK_n:
if (control && !repeat && down && !paused) {
if (control && !repeat && down) {
if (shift) {
collapse_panels(im);
} else if (im->key_repeat == 0) {
@@ -565,12 +558,12 @@ sc_input_manager_process_key(struct sc_input_manager *im,
}
return;
case SDLK_r:
if (control && !shift && !repeat && down && !paused) {
if (control && !shift && !repeat && down) {
rotate_device(im);
}
return;
case SDLK_k:
if (control && !shift && !repeat && down && !paused
if (control && !shift && !repeat && down
&& im->kp && im->kp->hid) {
// Only if the current keyboard is hid
open_hard_keyboard_settings(im);
@@ -581,7 +574,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
return;
}
if (!im->kp || paused) {
if (!im->kp) {
return;
}
@@ -626,33 +619,22 @@ sc_input_manager_process_key(struct sc_input_manager *im,
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
sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
const SDL_MouseMotionEvent *event) {
if (event->which == SDL_TOUCH_MOUSEID) {
// simulated from touch events, so it's a duplicate
return;
}
struct sc_mouse_motion_event evt = {
.position = sc_input_manager_get_position(im, event->x, event->y),
.position = {
.screen_size = im->screen->frame_size,
.point = sc_screen_convert_window_to_frame_coords(im->screen,
event->x,
event->y),
},
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
: POINTER_ID_GENERIC_FINGER,
.xrel = event->xrel,
@@ -713,16 +695,16 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
static void
sc_input_manager_process_mouse_button(struct sc_input_manager *im,
const SDL_MouseButtonEvent *event) {
bool control = im->controller;
if (event->which == SDL_TOUCH_MOUSEID) {
// simulated from touch events, so it's a duplicate
return;
}
bool control = im->controller;
bool paused = im->screen->paused;
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;
if (im->kp && event->button == SDL_BUTTON_X1) {
@@ -748,8 +730,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
}
// double-click on black borders resize to fit the device screen
bool video = im->screen->video;
if (video && event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
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);
@@ -766,14 +747,19 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
// otherwise, send the click event to the device
}
if (!im->mp || paused) {
if (!im->mp) {
return;
}
uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL);
struct sc_mouse_click_event evt = {
.position = sc_input_manager_get_position(im, event->x, event->y),
.position = {
.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),
.button = sc_mouse_button_from_sdl(event->button),
.pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE
@@ -848,7 +834,11 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
uint32_t buttons = SDL_GetMouseState(&mouse_x, &mouse_y);
struct sc_mouse_scroll_event evt = {
.position = sc_input_manager_get_position(im, mouse_x, mouse_y),
.position = {
.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)
.hscroll = CLAMP(event->preciseX, -1.0f, 1.0f),
.vscroll = CLAMP(event->preciseY, -1.0f, 1.0f),
@@ -895,10 +885,9 @@ void
sc_input_manager_handle_event(struct sc_input_manager *im,
const SDL_Event *event) {
bool control = im->controller;
bool paused = im->screen->paused;
switch (event->type) {
case SDL_TEXTINPUT:
if (!im->kp || paused) {
if (!im->kp) {
break;
}
sc_input_manager_process_text_input(im, &event->text);
@@ -910,13 +899,13 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
sc_input_manager_process_key(im, &event->key);
break;
case SDL_MOUSEMOTION:
if (!im->mp || paused) {
if (!im->mp) {
break;
}
sc_input_manager_process_mouse_motion(im, &event->motion);
break;
case SDL_MOUSEWHEEL:
if (!im->mp || paused) {
if (!im->mp) {
break;
}
sc_input_manager_process_mouse_wheel(im, &event->wheel);
@@ -930,7 +919,7 @@ sc_input_manager_handle_event(struct sc_input_manager *im,
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
if (!im->mp || paused) {
if (!im->mp) {
break;
}
sc_input_manager_process_touch(im, &event->tfinger);

View File

@@ -89,7 +89,6 @@ const struct scrcpy_options scrcpy_options_default = {
.kill_adb_on_close = false,
.camera_high_speed = false,
.list = 0,
.window = true,
};
enum sc_orientation

View File

@@ -279,7 +279,6 @@ struct scrcpy_options {
#define SC_OPTION_LIST_CAMERAS 0x4
#define SC_OPTION_LIST_CAMERA_SIZES 0x8
uint8_t list;
bool window;
};
extern const struct scrcpy_options scrcpy_options_default;

View File

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

View File

@@ -19,18 +19,10 @@ struct sc_receiver {
struct sc_acksync *acksync;
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
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket,
const struct sc_receiver_callbacks *cbs, void *cbs_userdata);
sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket);
void
sc_receiver_destroy(struct sc_receiver *receiver);

View File

@@ -106,6 +106,7 @@ static BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
static void
sdl_set_hints(const char *render_driver) {
if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) {
LOGW("Could not set render driver");
}
@@ -174,9 +175,6 @@ event_loop(struct scrcpy *s) {
case SC_EVENT_DEMUXER_ERROR:
LOGE("Demuxer error");
return SCRCPY_EXIT_FAILURE;
case SC_EVENT_CONTROLLER_ERROR:
LOGE("Controller error");
return SCRCPY_EXIT_FAILURE;
case SC_EVENT_RECORDER_ERROR:
LOGE("Recorder error");
return SCRCPY_EXIT_FAILURE;
@@ -268,16 +266,6 @@ 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
sc_server_on_connection_failed(struct sc_server *server, void *userdata) {
(void) server;
@@ -324,10 +312,6 @@ scrcpy_generate_scid(void) {
enum scrcpy_exit_code
scrcpy(struct scrcpy_options *options) {
static struct scrcpy scrcpy;
#ifndef NDEBUG
// Detect missing initializations
memset(&scrcpy, 42, sizeof(scrcpy));
#endif
struct scrcpy *s = &scrcpy;
// Minimal SDL initialization
@@ -421,12 +405,6 @@ scrcpy(struct scrcpy_options *options) {
return SCRCPY_EXIT_FAILURE;
}
if (options->window) {
// Set hints before starting the server thread to avoid race conditions
// in SDL
sdl_set_hints(options->render_driver);
}
if (!sc_server_start(&s->server)) {
goto end;
}
@@ -443,7 +421,11 @@ scrcpy(struct scrcpy_options *options) {
assert(!options->video_playback || options->video);
assert(!options->audio_playback || options->audio);
if (options->window ||
if (options->video_playback) {
sdl_set_hints(options->render_driver);
}
if (options->video_playback ||
(options->control && options->clipboard_autosync)) {
// Initialize the video subsystem even if --no-video or
// --no-video-playback is passed so that clipboard synchronization
@@ -566,12 +548,7 @@ scrcpy(struct scrcpy_options *options) {
struct sc_mouse_processor *mp = NULL;
if (options->control) {
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)) {
if (!sc_controller_init(&s->controller, s->server.control_socket)) {
goto end;
}
controller_initialized = true;
@@ -702,12 +679,11 @@ scrcpy(struct scrcpy_options *options) {
// There is a controller if and only if control is enabled
assert(options->control == !!controller);
if (options->window) {
if (options->video_playback) {
const char *window_title =
options->window_title ? options->window_title : info->device_name;
struct sc_screen_params screen_params = {
.video = options->video_playback,
.controller = controller,
.fp = fp,
.kp = kp,
@@ -729,15 +705,12 @@ scrcpy(struct scrcpy_options *options) {
.start_fps_counter = options->start_fps_counter,
};
struct sc_frame_source *src;
if (options->video_playback) {
src = &s->video_decoder.frame_source;
if (options->display_buffer) {
sc_delay_buffer_init(&s->display_buffer,
options->display_buffer, true);
sc_frame_source_add_sink(src, &s->display_buffer.frame_sink);
src = &s->display_buffer.frame_source;
}
struct sc_frame_source *src = &s->video_decoder.frame_source;
if (options->display_buffer) {
sc_delay_buffer_init(&s->display_buffer, options->display_buffer,
true);
sc_frame_source_add_sink(src, &s->display_buffer.frame_sink);
src = &s->display_buffer.frame_source;
}
if (!sc_screen_init(&s->screen, &screen_params)) {
@@ -745,9 +718,7 @@ scrcpy(struct scrcpy_options *options) {
}
screen_initialized = true;
if (options->video_playback) {
sc_frame_source_add_sink(src, &s->screen.frame_sink);
}
sc_frame_source_add_sink(src, &s->screen.frame_sink);
}
if (options->audio_playback) {
@@ -829,12 +800,9 @@ scrcpy(struct scrcpy_options *options) {
ret = event_loop(s);
LOGD("quit...");
if (options->video_playback) {
// Close the window immediately on closing, because screen_destroy()
// may only be called once the video demuxer thread is joined (it may
// take time)
sc_screen_hide_window(&s->screen);
}
// Close the window immediately on closing, because screen_destroy() may
// only be called once the video demuxer thread is joined (it may take time)
sc_screen_hide_window(&s->screen);
end:
if (timeout_started) {

View File

@@ -205,8 +205,6 @@ sc_screen_toggle_mouse_capture(struct sc_screen *screen) {
static void
sc_screen_update_content_rect(struct sc_screen *screen) {
assert(screen->video);
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
@@ -248,8 +246,6 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
// changed, so that the content rectangle is recomputed
static void
sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
assert(screen->video);
if (update_content_rect) {
sc_screen_update_content_rect(screen);
}
@@ -259,13 +255,6 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
(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__)
# define CONTINUOUS_RESIZING_WORKAROUND
#endif
@@ -279,8 +268,6 @@ sc_screen_render_novideo(struct sc_screen *screen) {
static int
event_watcher(void *data, SDL_Event *event) {
struct sc_screen *screen = data;
assert(screen->video);
if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
// In practice, it seems to always be called from the same thread in
@@ -339,7 +326,6 @@ sc_screen_frame_sink_close(struct sc_frame_sink *sink) {
static bool
sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
struct sc_screen *screen = DOWNCAST(sink);
assert(screen->video);
bool previous_skipped;
bool ok = sc_frame_buffer_push(&screen->fb, frame, &previous_skipped);
@@ -376,11 +362,6 @@ sc_screen_init(struct sc_screen *screen,
screen->maximized = false;
screen->minimized = false;
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.y = params->window_y;
@@ -398,75 +379,41 @@ sc_screen_init(struct sc_screen *screen,
goto error_destroy_frame_buffer;
}
if (screen->video) {
screen->orientation = params->orientation;
if (screen->orientation != SC_ORIENTATION_0) {
LOGI("Initial display orientation set to %s",
sc_orientation_get_name(screen->orientation));
}
screen->orientation = params->orientation;
if (screen->orientation != SC_ORIENTATION_0) {
LOGI("Initial display orientation set to %s",
sc_orientation_get_name(screen->orientation));
}
uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI;
uint32_t window_flags = SDL_WINDOW_HIDDEN
| SDL_WINDOW_RESIZABLE
| SDL_WINDOW_ALLOW_HIGHDPI;
if (params->always_on_top) {
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
}
if (params->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
screen->window = SDL_CreateWindow(title, x, y, width, height, window_flags);
screen->window =
SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags);
if (!screen->window) {
LOGE("Could not create window: %s", SDL_GetError());
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();
if (icon) {
SDL_SetWindowIcon(screen->window, icon);
} else if (params->video) {
// just a warning
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;
} else {
LOGW("Could not load icon");
}
screen->frame = av_frame_alloc();
@@ -490,9 +437,7 @@ sc_screen_init(struct sc_screen *screen,
sc_input_manager_init(&screen->im, &im_params);
#ifdef CONTINUOUS_RESIZING_WORKAROUND
if (screen->video) {
SDL_AddEventWatch(event_watcher, screen);
}
SDL_AddEventWatch(event_watcher, screen);
#endif
static const struct sc_frame_sink_ops ops = {
@@ -507,11 +452,6 @@ sc_screen_init(struct sc_screen *screen,
screen->open = false;
#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;
error_destroy_display:
@@ -582,8 +522,6 @@ sc_screen_destroy(struct sc_screen *screen) {
static void
resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
struct sc_size new_content_size) {
assert(screen->video);
struct sc_size window_size = get_window_size(screen);
struct sc_size target_size = {
.width = (uint32_t) window_size.width * new_content_size.width
@@ -597,8 +535,6 @@ resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
static void
set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
assert(screen->video);
if (!screen->fullscreen && !screen->maximized && !screen->minimized) {
resize_for_content(screen, screen->content_size, new_content_size);
} else if (!screen->resize_pending) {
@@ -613,8 +549,6 @@ set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
static void
apply_pending_resize(struct sc_screen *screen) {
assert(screen->video);
assert(!screen->fullscreen);
assert(!screen->maximized);
assert(!screen->minimized);
@@ -628,8 +562,6 @@ apply_pending_resize(struct sc_screen *screen) {
void
sc_screen_set_orientation(struct sc_screen *screen,
enum sc_orientation orientation) {
assert(screen->video);
if (orientation == screen->orientation) {
return;
}
@@ -664,8 +596,6 @@ sc_screen_init_size(struct sc_screen *screen) {
// recreate the texture and resize the window if the frame size has changed
static enum sc_display_result
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
&& screen->frame_size.height == new_frame_size.height) {
return SC_DISPLAY_RESULT_OK;
@@ -684,12 +614,13 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
}
static bool
sc_screen_apply_frame(struct sc_screen *screen) {
assert(screen->video);
sc_screen_update_frame(struct sc_screen *screen) {
av_frame_unref(screen->frame);
sc_frame_buffer_consume(&screen->fb, screen->frame);
AVFrame *frame = screen->frame;
sc_fps_counter_add_rendered_frame(&screen->fps_counter);
AVFrame *frame = screen->frame;
struct sc_size new_frame_size = {frame->width, frame->height};
enum sc_display_result res = prepare_for_frame(screen, new_frame_size);
if (res == SC_DISPLAY_RESULT_ERROR) {
@@ -724,62 +655,8 @@ sc_screen_apply_frame(struct sc_screen *screen) {
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
sc_screen_switch_fullscreen(struct sc_screen *screen) {
assert(screen->video);
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
@@ -797,8 +674,6 @@ sc_screen_switch_fullscreen(struct sc_screen *screen) {
void
sc_screen_resize_to_fit(struct sc_screen *screen) {
assert(screen->video);
if (screen->fullscreen || screen->maximized || screen->minimized) {
return;
}
@@ -823,8 +698,6 @@ sc_screen_resize_to_fit(struct sc_screen *screen) {
void
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
assert(screen->video);
if (screen->fullscreen || screen->minimized) {
return;
}
@@ -868,13 +741,6 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
return true;
}
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) {
// Do nothing
return true;
@@ -978,8 +844,6 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
struct sc_point
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y) {
assert(screen->video);
enum sc_orientation orientation = screen->orientation;
int32_t w = screen->content_size.width;

View File

@@ -26,8 +26,6 @@ struct sc_screen {
bool open; // track the open/close state to assert correct behavior
#endif
bool video;
struct sc_display display;
struct sc_input_manager im;
struct sc_frame_buffer fb;
@@ -66,14 +64,9 @@ struct sc_screen {
SDL_Keycode mouse_capture_key_pressed;
AVFrame *frame;
bool paused;
AVFrame *resume_frame;
};
struct sc_screen_params {
bool video;
struct sc_controller *controller;
struct sc_file_pusher *fp;
struct sc_key_processor *kp;
@@ -142,10 +135,6 @@ void
sc_screen_set_orientation(struct sc_screen *screen,
enum sc_orientation orientation);
// set the display pause state
void
sc_screen_set_paused(struct sc_screen *screen, bool paused);
// react to SDL events
// If this function returns false, scrcpy must exit with an error.
bool

View File

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

View File

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

View File

@@ -28,17 +28,10 @@ To disable only the audio playback, see [no playback](video.md#no-playback).
## Audio only
To play audio only, disable video and control:
To play audio only, disable the video:
```bash
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
scrcpy --no-video
# interrupt with Ctrl+C
```

View File

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

View File

@@ -67,6 +67,14 @@ computer.
An option `--tcpip` allows to configure the connection automatically. There are
two variants.
If the device (accessible at 192.168.1.1 in this example) already listens on a
port (typically 5555) for incoming _adb_ connections, then run:
```bash
scrcpy --tcpip=192.168.1.1 # default port is 5555
scrcpy --tcpip=192.168.1.1:5555
```
If _adb_ TCP/IP mode is disabled on the device (or if you don't know the IP
address), connect the device over USB, then run:
@@ -77,14 +85,6 @@ scrcpy --tcpip # without arguments
It will automatically find the device IP address and adb port, enable TCP/IP
mode if necessary, then connect to the device before starting.
If the device (accessible at 192.168.1.1 in this example) already listens on a
port (typically 5555) for incoming _adb_ connections, then run:
```bash
scrcpy --tcpip=192.168.1.1 # default port is 5555
scrcpy --tcpip=192.168.1.1:5555
```
### Manual

View File

@@ -15,31 +15,6 @@ scrcpy -n # short version
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
Any time the Android clipboard changes, it is automatically synchronized to the
@@ -92,16 +67,12 @@ More precisely, hold down <kbd>Ctrl</kbd> while pressing the left-click button.
Until the left-click button is released, all mouse movements scale and rotate
the content (if supported by the app) relative to the center of the screen.
https://github.com/Genymobile/scrcpy/assets/543275/26c4a920-9805-43f1-8d4c-608752d04767
To simulate a tilt gesture: <kbd>Shift</kbd>+_click-and-move-up-or-down_.
https://github.com/Genymobile/scrcpy/assets/543275/1e252341-4a90-4b29-9d11-9153b324669f
Technically, _scrcpy_ generates additional touch events from a "virtual finger"
at a location inverted through the center of the screen. When pressing
<kbd>Ctrl</kbd> the _x_ and _y_ coordinates are inverted. Using <kbd>Shift</kbd>
only inverts _x_.
<kbd>Ctrl</kbd> the x and y coordinates are inverted. Using <kbd>Shift</kbd>
only inverts x.
This only works for the default mouse mode (`--mouse=sdk`).

View File

@@ -1,21 +1,19 @@
# OTG
By default, _scrcpy_ injects input events at the Android API level. As an
alternative, it is possible to send HID events, so that scrcpy behaves as if it
was a [physical keyboard] and/or a [physical mouse] connected to the Android
device (see [keyboard](keyboard.md) and [mouse](mouse.md)).
alternative, when connected over USB, it is possible to send HID events, so that
scrcpy behaves as if it was a physical keyboard and/or mouse connected to the
Android device.
[physical keyboard]: keyboard.md#physical-keyboard-simulation
[physical mouse]: physical-keyboard-simulation
A special mode allows to control the device without mirroring, using AOA
[keyboard](keyboard.md#aoa) and [mouse](mouse.md#aoa). Therefore, it is possible
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.
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.
In this mode, `adb` (USB debugging) is not necessary, and mirroring is disabled.
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.
This is similar to `--keyboard=aoa --mouse=aoa`, but without mirroring.
To enable OTG mode:
@@ -25,7 +23,7 @@ scrcpy --otg
scrcpy --otg -s 0123456789abcdef
```
It is possible to disable keyboard or mouse:
It is possible to disable HID keyboard or HID mouse:
```bash
scrcpy --otg --keyboard=disabled
@@ -37,22 +35,3 @@ It only works if the device is connected over USB.
## 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,10 +58,12 @@ orientation](video.md#orientation).
## No playback
To disable playback and control while recording:
To disable playback while recording:
```bash
scrcpy --no-playback --no-control --record=file.mp4
scrcpy --no-playback --record=file.mp4
scrcpy -Nr file.mkv
# interrupt recording with Ctrl+C
```
It is also possible to disable video and audio playback separately:
@@ -71,13 +73,6 @@ It is also possible to disable video and audio playback separately:
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
To limit the recording time:

View File

@@ -28,8 +28,6 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| 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 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 remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_

View File

@@ -1,14 +1,5 @@
# 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
By default, the window title is the device model. It can be changed:

View File

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

View File

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

View File

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

View File

@@ -62,38 +62,38 @@ build-server:
meson setup "$(SERVER_BUILD_DIR)" --buildtype release -Dcompile_app=false )
ninja -C "$(SERVER_BUILD_DIR)"
prepare-deps-win32:
@app/deps/adb.sh win32
@app/deps/sdl.sh win32
@app/deps/ffmpeg.sh win32
@app/deps/libusb.sh win32
prepare-deps:
@app/prebuilt-deps/prepare-adb.sh
@app/prebuilt-deps/prepare-sdl.sh
@app/prebuilt-deps/prepare-ffmpeg.sh
@app/prebuilt-deps/prepare-libusb.sh
prepare-deps-win64:
@app/deps/adb.sh win64
@app/deps/sdl.sh win64
@app/deps/ffmpeg.sh win64
@app/deps/libusb.sh win64
build-win32: prepare-deps-win32
build-win32: prepare-deps
rm -rf "$(WIN32_BUILD_DIR)"
mkdir -p "$(WIN32_BUILD_DIR)/local"
cp -r app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-3/win32/. "$(WIN32_BUILD_DIR)/local/"
cp -r app/prebuilt-deps/data/SDL2-2.28.5/i686-w64-mingw32/. "$(WIN32_BUILD_DIR)/local/"
cp -r app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-Win32/. "$(WIN32_BUILD_DIR)/local/"
meson setup "$(WIN32_BUILD_DIR)" \
--pkg-config-path="app/deps/work/install/win32/lib/pkgconfig" \
-Dc_args="-I$(PWD)/app/deps/work/install/win32/include" \
-Dc_link_args="-L$(PWD)/app/deps/work/install/win32/lib" \
--pkg-config-path="$(WIN32_BUILD_DIR)/local/lib/pkgconfig" \
-Dc_args="-I$(PWD)/$(WIN32_BUILD_DIR)/local/include" \
-Dc_link_args="-L$(PWD)/$(WIN32_BUILD_DIR)/local/lib" \
--cross-file=cross_win32.txt \
--buildtype=release --strip -Db_lto=true \
-Dcompile_server=false \
-Dportable=true
ninja -C "$(WIN32_BUILD_DIR)"
build-win64: prepare-deps-win64
build-win64: prepare-deps
rm -rf "$(WIN64_BUILD_DIR)"
mkdir -p "$(WIN64_BUILD_DIR)/local"
cp -r app/prebuilt-deps/data/ffmpeg-6.1-scrcpy-3/win64/. "$(WIN64_BUILD_DIR)/local/"
cp -r app/prebuilt-deps/data/SDL2-2.28.5/x86_64-w64-mingw32/. "$(WIN64_BUILD_DIR)/local/"
cp -r app/prebuilt-deps/data/libusb-1.0.26/libusb-MinGW-x64/. "$(WIN64_BUILD_DIR)/local/"
meson setup "$(WIN64_BUILD_DIR)" \
--pkg-config-path="app/deps/work/install/win64/lib/pkgconfig" \
-Dc_args="-I$(PWD)/app/deps/work/install/win64/include" \
-Dc_link_args="-L$(PWD)/app/deps/work/install/win64/lib" \
--pkg-config-path="$(WIN64_BUILD_DIR)/local/lib/pkgconfig" \
-Dc_args="-I$(PWD)/$(WIN64_BUILD_DIR)/local/include" \
-Dc_link_args="-L$(PWD)/$(WIN64_BUILD_DIR)/local/lib" \
--cross-file=cross_win64.txt \
--buildtype=release --strip -Db_lto=true \
-Dcompile_server=false \
@@ -108,8 +108,10 @@ dist-win32: build-server build-win32
cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/deps/work/install/win32/bin/*.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/deps/work/install/win32/bin/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp "$(WIN32_BUILD_DIR)"/local/bin/*.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
dist-win64: build-server build-win64
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
@@ -119,8 +121,10 @@ dist-win64: build-server build-win64
cp app/data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/deps/work/install/win64/bin/*.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/deps/work/install/win64/bin/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-34.0.5/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp "$(WIN64_BUILD_DIR)"/local/bin/*.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
zip-win32: dist-win32
cd "$(DIST)"; \

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,6 @@ package com.genymobile.scrcpy;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.os.Build;
import android.os.Looper;
import android.os.SystemClock;
import android.view.Surface;
@@ -48,7 +47,7 @@ public class SurfaceEncoder implements AsyncProcessor {
this.downsizeOnError = downsizeOnError;
}
private void streamCapture() throws IOException, ConfigurationException {
private void streamScreen() throws IOException, ConfigurationException {
Codec codec = streamer.getCodec();
MediaCodec mediaCodec = createMediaCodec(codec, encoderName);
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
@@ -221,9 +220,6 @@ public class SurfaceEncoder implements AsyncProcessor {
// 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_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);
// 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
@@ -254,7 +250,7 @@ public class SurfaceEncoder implements AsyncProcessor {
Looper.prepare();
try {
streamCapture();
streamScreen();
} catch (ConfigurationException e) {
// Do not print stack trace, a user-friendly error-message has already been logged
} catch (IOException e) {

View File

@@ -12,15 +12,12 @@ import java.lang.reflect.Method;
public final class WindowManager {
private final IInterface manager;
private Method getRotationMethod;
private Method freezeRotationMethod;
private Method freezeDisplayRotationMethod;
private int freezeDisplayRotationMethodVersion;
private Method isRotationFrozenMethod;
private Method isDisplayRotationFrozenMethod;
private int isDisplayRotationFrozenMethodVersion;
private Method thawRotationMethod;
private Method thawDisplayRotationMethod;
private int thawDisplayRotationMethodVersion;
static WindowManager create() {
IInterface manager = ServiceManager.getService("window", "android.view.IWindowManager");
@@ -46,61 +43,50 @@ public final class WindowManager {
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 {
if (freezeDisplayRotationMethod == null) {
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;
}
}
freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class);
}
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 {
if (isDisplayRotationFrozenMethod == null) {
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;
}
isDisplayRotationFrozenMethod = manager.getClass().getMethod("isDisplayRotationFrozen", int.class);
}
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 {
if (thawDisplayRotationMethod == null) {
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;
}
}
thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class);
}
return thawDisplayRotationMethod;
}
@@ -117,21 +103,16 @@ public final class WindowManager {
public void freezeRotation(int displayId, int rotation) {
try {
Method method = getFreezeDisplayRotationMethod();
switch (freezeDisplayRotationMethodVersion) {
case 0:
method.invoke(manager, displayId, rotation, "scrcpy#freezeRotation");
break;
case 1:
method.invoke(manager, displayId, rotation);
break;
default:
if (displayId != 0) {
Ln.e("Secondary display rotation not supported on this device");
return;
}
try {
Method method = getFreezeDisplayRotationMethod();
method.invoke(manager, displayId, rotation);
} catch (ReflectiveOperationException e) {
if (displayId == 0) {
Method method = getFreezeRotationMethod();
method.invoke(manager, rotation);
break;
} else {
Ln.e("Could not invoke method", e);
}
}
} catch (ReflectiveOperationException e) {
Ln.e("Could not invoke method", e);
@@ -140,16 +121,17 @@ public final class WindowManager {
public boolean isRotationFrozen(int displayId) {
try {
Method method = getIsDisplayRotationFrozenMethod();
switch (isDisplayRotationFrozenMethodVersion) {
case 0:
return (boolean) method.invoke(manager, displayId);
default:
if (displayId != 0) {
Ln.e("Secondary display rotation not supported on this device");
return false;
}
try {
Method method = getIsDisplayRotationFrozenMethod();
return (boolean) method.invoke(manager, displayId);
} catch (ReflectiveOperationException e) {
if (displayId == 0) {
Method method = getIsRotationFrozenMethod();
return (boolean) method.invoke(manager);
} else {
Ln.e("Could not invoke method", e);
return false;
}
}
} catch (ReflectiveOperationException e) {
Ln.e("Could not invoke method", e);
@@ -159,21 +141,16 @@ public final class WindowManager {
public void thawRotation(int displayId) {
try {
Method method = getThawDisplayRotationMethod();
switch (thawDisplayRotationMethodVersion) {
case 0:
method.invoke(manager, displayId, "scrcpy#thawRotation");
break;
case 1:
method.invoke(manager, displayId);
break;
default:
if (displayId != 0) {
Ln.e("Secondary display rotation not supported on this device");
return;
}
try {
Method method = getThawDisplayRotationMethod();
method.invoke(manager, displayId);
} catch (ReflectiveOperationException e) {
if (displayId == 0) {
Method method = getThawRotationMethod();
method.invoke(manager);
break;
} else {
Ln.e("Could not invoke method", e);
}
}
} catch (ReflectiveOperationException e) {
Ln.e("Could not invoke method", e);
@@ -189,10 +166,6 @@ public final class WindowManager {
cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, displayId);
} catch (NoSuchMethodException e) {
// old version
if (displayId != 0) {
Ln.e("Secondary display rotation not supported on this device");
return;
}
cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher);
}
} catch (Exception e) {