Compare commits

..

3 Commits

Author SHA1 Message Date
7a86156503 Support custom virtual display refresh rates
Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-11-20 20:11:17 +01:00
1b5d88368a Keep DisplayManager instance across calls
Do not create a new android.hardware.display.DisplayManager for every
creation of a new virtual display.
2024-11-20 20:11:03 +01:00
daba00a819 Dissociate virtual display size and capture size
Allow capturing virtual displays at a lower resolution using
-m/--max-size.

In the original implementation in #5370, the virtual display size was
necessarily the same as the capture size. The --max-size value was only
allowed to determine the virtual display size when no explicit size was
provided.

Since the dpi was scaled down accordingly, it is often better to create
a virtual display at the target capture size directly. However, not
everything is rendered according to the virtual display DPI. For
example, a page in Firefox is rendered too big on small virtual
displays. Thus, it makes sense to be able create a virtual display at a
given size, and capture it at a lower resolution with --max-size. This
is now possible using OpenGL filters.

Therefore, change the behavior of --max-size for virtual displays:
 - it does not impact --new-display without size argument anymore (the
   virtual display size is the main display size);
 - it is used to limit the capture size (whether an explicit size is
   provided or not).

This new behavior is consistent with main display capture.

Refs #5370 comment <https://github.com/Genymobile/scrcpy/pull/5370#issuecomment-2438944401>
Refs <https://github.com/Genymobile/scrcpy/pull/5370>
2024-11-20 13:01:57 +01:00
59 changed files with 757 additions and 1460 deletions

View File

@ -6,15 +6,11 @@ on:
name:
description: 'Version name (default is ref name)'
env:
# $VERSION is used by release scripts
VERSION: ${{ github.event.inputs.name || github.ref_name }}
jobs:
test-scrcpy-server:
build-scrcpy-server:
runs-on: ubuntu-latest
env:
GRADLE: gradle # use native gradle instead of ./gradlew in scripts
GRADLE: gradle # use native gradle instead of ./gradlew in release.mk
steps:
- name: Checkout code
uses: actions/checkout@v4
@ -26,45 +22,16 @@ jobs:
java-version: '17'
- name: Test scrcpy-server
run: release/test_server.sh
run: make -f release.mk test-server
build-scrcpy-server:
runs-on: ubuntu-latest
env:
GRADLE: gradle # use native gradle instead of ./gradlew in scripts
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build scrcpy-server
run: make -f release.mk build-server
- name: Setup JDK
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
- name: Build
run: release/build_server.sh
- name: Upload artifact
- name: Upload scrcpy-server artifact
uses: actions/upload-artifact@v4
with:
name: scrcpy-server
path: release/work/build-server/server/scrcpy-server
test-build-scrcpy-server-without-gradle:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup JDK
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
- name: Build without gradle
run: server/build_without_gradle.sh
path: build-server/server/scrcpy-server
test-client:
runs-on: ubuntu-latest
@ -74,52 +41,18 @@ jobs:
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
libv4l-dev
- name: Test
run: release/test_client.sh
build-linux-x86_64:
runs-on: ubuntu-latest
steps:
- name: Check architecture
run: |
arch=$(uname -m)
if [[ "$arch" != x86_64 ]]
then
echo "Unexpected architecture: $arch" >&2
exit 1
fi
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
libv4l-dev
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev
- name: Build
run: release/build_linux.sh x86_64
# upload-artifact does not preserve permissions
- name: Tar
run: |
cd release/work/build-linux-x86_64
mkdir dist-tar
cd dist-tar
tar -C .. -cvf dist.tar.gz dist/
meson setup d -Db_sanitize=address,undefined
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: build-linux-x86_64-intermediate
path: release/work/build-linux-x86_64/dist-tar/
- name: Test
run: |
meson test -Cd
build-win32:
runs-on: ubuntu-latest
@ -129,6 +62,7 @@ jobs:
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
@ -137,22 +71,14 @@ jobs:
- name: Workaround for old meson version run by Github Actions
run: sed -i 's/^pkg-config/pkgconfig/' cross_win32.txt
- name: Build
run: release/build_windows.sh 32
- name: Build scrcpy win32
run: make -f release.mk build-win32
# upload-artifact does not preserve permissions
- name: Tar
run: |
cd release/work/build-win32
mkdir dist-tar
cd dist-tar
tar -C .. -cvf dist.tar.gz dist/
- name: Upload artifact
- name: Upload build-win32 artifact
uses: actions/upload-artifact@v4
with:
name: build-win32-intermediate
path: release/work/build-win32/dist-tar/
path: build-win32/dist/
build-win64:
runs-on: ubuntu-latest
@ -162,6 +88,7 @@ jobs:
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
@ -170,137 +97,24 @@ jobs:
- name: Workaround for old meson version run by Github Actions
run: sed -i 's/^pkg-config/pkgconfig/' cross_win64.txt
- name: Build
run: release/build_windows.sh 64
- name: Build scrcpy win64
run: make -f release.mk build-win64
# upload-artifact does not preserve permissions
- name: Tar
run: |
cd release/work/build-win64
mkdir dist-tar
cd dist-tar
tar -C .. -cvf dist.tar.gz dist/
- name: Upload artifact
- name: Upload build-win64 artifact
uses: actions/upload-artifact@v4
with:
name: build-win64-intermediate
path: release/work/build-win64/dist-tar/
path: build-win64/dist/
build-macos-aarch64:
runs-on: macos-latest
steps:
- name: Check architecture
run: |
arch=$(uname -m)
if [[ "$arch" != arm64 ]]
then
echo "Unexpected architecture: $arch" >&2
exit 1
fi
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: |
brew install meson ninja nasm libiconv zlib automake autoconf \
libtool
- name: Build
run: release/build_macos.sh aarch64
# upload-artifact does not preserve permissions
- name: Tar
run: |
cd release/work/build-macos-aarch64
mkdir dist-tar
cd dist-tar
tar -C .. -cvf dist.tar.gz dist/
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: build-macos-aarch64-intermediate
path: release/work/build-macos-aarch64/dist-tar/
build-macos-x86_64:
runs-on: macos-13
steps:
- name: Check architecture
run: |
arch=$(uname -m)
if [[ "$arch" != x86_64 ]]
then
echo "Unexpected architecture: $arch" >&2
exit 1
fi
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: brew install meson ninja nasm libiconv zlib automake
# autoconf and libtool are already installed on macos-13
- name: Build
run: release/build_macos.sh x86_64
# upload-artifact does not preserve permissions
- name: Tar
run: |
cd release/work/build-macos-x86_64
mkdir dist-tar
cd dist-tar
tar -C .. -cvf dist.tar.gz dist/
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: build-macos-x86_64-intermediate
path: release/work/build-macos-x86_64/dist-tar/
package-linux-x86_64:
needs:
- build-scrcpy-server
- build-linux-x86_64
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download scrcpy-server
uses: actions/download-artifact@v4
with:
name: scrcpy-server
path: release/work/build-server/server/
- name: Download build-linux-x86_64
uses: actions/download-artifact@v4
with:
name: build-linux-x86_64-intermediate
path: release/work/build-linux-x86_64/dist-tar/
# upload-artifact does not preserve permissions
- name: Detar
run: |
cd release/work/build-linux-x86_64
tar xf dist-tar/dist.tar.gz
- name: Package
run: release/package_client.sh linux-x86_64 tar.gz
- name: Upload release
uses: actions/upload-artifact@v4
with:
name: release-linux-x86_64
path: release/output/
package-win32:
package:
needs:
- build-scrcpy-server
- build-win32
- build-win64
runs-on: ubuntu-latest
env:
# $VERSION is used by release.mk
VERSION: ${{ github.event.inputs.name || github.ref_name }}
steps:
- name: Checkout code
uses: actions/checkout@v4
@ -309,194 +123,25 @@ jobs:
uses: actions/download-artifact@v4
with:
name: scrcpy-server
path: release/work/build-server/server/
path: build-server/server/
- name: Download build-win32
uses: actions/download-artifact@v4
with:
name: build-win32-intermediate
path: release/work/build-win32/dist-tar/
# upload-artifact does not preserve permissions
- name: Detar
run: |
cd release/work/build-win32
tar xf dist-tar/dist.tar.gz
- name: Package
run: release/package_client.sh win32 zip
- name: Upload release
uses: actions/upload-artifact@v4
with:
name: release-win32
path: release/output/
package-win64:
needs:
- build-scrcpy-server
- build-win64
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download scrcpy-server
uses: actions/download-artifact@v4
with:
name: scrcpy-server
path: release/work/build-server/server/
path: build-win32/dist/
- name: Download build-win64
uses: actions/download-artifact@v4
with:
name: build-win64-intermediate
path: release/work/build-win64/dist-tar/
# upload-artifact does not preserve permissions
- name: Detar
run: |
cd release/work/build-win64
tar xf dist-tar/dist.tar.gz
path: build-win64/dist/
- name: Package
run: release/package_client.sh win64 zip
- name: Upload release
uses: actions/upload-artifact@v4
with:
name: release-win64
path: release/output
package-macos-aarch64:
needs:
- build-scrcpy-server
- build-macos-aarch64
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download scrcpy-server
uses: actions/download-artifact@v4
with:
name: scrcpy-server
path: release/work/build-server/server/
- name: Download build-macos-aarch64
uses: actions/download-artifact@v4
with:
name: build-macos-aarch64-intermediate
path: release/work/build-macos-aarch64/dist-tar/
# upload-artifact does not preserve permissions
- name: Detar
run: |
cd release/work/build-macos-aarch64
tar xf dist-tar/dist.tar.gz
- name: Package
run: release/package_client.sh macos-aarch64 tar.gz
- name: Upload release
uses: actions/upload-artifact@v4
with:
name: release-macos-aarch64
path: release/output/
package-macos-x86_64:
needs:
- build-scrcpy-server
- build-macos-x86_64
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download scrcpy-server
uses: actions/download-artifact@v4
with:
name: scrcpy-server
path: release/work/build-server/server/
- name: Download build-macos
uses: actions/download-artifact@v4
with:
name: build-macos-x86_64-intermediate
path: release/work/build-macos-x86_64/dist-tar/
# upload-artifact does not preserve permissions
- name: Detar
run: |
cd release/work/build-macos-x86_64
tar xf dist-tar/dist.tar.gz
- name: Package
run: release/package_client.sh macos-x86_64 tar.gz
- name: Upload release
uses: actions/upload-artifact@v4
with:
name: release-macos-x86_64
path: release/output/
release:
needs:
- build-scrcpy-server
- package-linux-x86_64
- package-win32
- package-win64
- package-macos-aarch64
- package-macos-x86_64
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download scrcpy-server
uses: actions/download-artifact@v4
with:
name: scrcpy-server
path: release/work/build-server/server/
- name: Download release-linux-x86_64
uses: actions/download-artifact@v4
with:
name: release-linux-x86_64
path: release/output/
- name: Download release-win32
uses: actions/download-artifact@v4
with:
name: release-win32
path: release/output/
- name: Download release-win64
uses: actions/download-artifact@v4
with:
name: release-win64
path: release/output/
- name: Download release-macos-aarch64
uses: actions/download-artifact@v4
with:
name: release-macos-aarch64
path: release/output/
- name: Download release-macos-x86_64
uses: actions/download-artifact@v4
with:
name: release-macos-x86_64
path: release/output/
- name: Package server
run: release/package_server.sh
- name: Generate checksums
run: release/generate_checksums.sh
run: make -f release.mk package
- name: Upload release artifact
uses: actions/upload-artifact@v4
with:
name: scrcpy-release-${{ env.VERSION }}
path: release/output
path: release-${{ env.VERSION }}

View File

@ -2,7 +2,7 @@
source for the project. Do not download releases from random websites, even if
their name contains `scrcpy`.**
# scrcpy (v3.0)
# scrcpy (v2.7)
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
@ -74,7 +74,7 @@ Note that USB debugging is not required to run scrcpy in [OTG mode](doc/otg.md).
## Get the app
- [Linux](doc/linux.md)
- [Windows](doc/windows.md) (read [how to run](doc/windows.md#run))
- [Windows](doc/windows.md)
- [macOS](doc/macos.md)
@ -141,7 +141,7 @@ documented in the following pages:
- [Device](doc/device.md)
- [Window](doc/window.md)
- [Recording](doc/recording.md)
- [Virtual display](doc/virtual_display.md)
- [Virtual display](doc/virtual_displays.md)
- [Tunnels](doc/tunnels.md)
- [OTG](doc/otg.md)
- [Camera](doc/camera.md)
@ -181,7 +181,6 @@ to your problem immediately.
You can also use:
- Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy)
- BlueSky: [`@scrcpy.bsky.social`](https://bsky.app/profile/scrcpy.bsky.social)
- Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app)

View File

@ -6,7 +6,7 @@ cd "$DEPS_DIR"
VERSION=35.0.2
FILENAME=platform-tools_r$VERSION-win.zip
PROJECT_DIR=platform-tools-$VERSION-windows
PROJECT_DIR=platform-tools-$VERSION
SHA256SUM=2975a3eac0b19182748d64195375ad056986561d994fffbdc64332a516300bb9
cd "$SOURCES_DIR"
@ -27,6 +27,6 @@ else
rmdir "$ZIP_PREFIX"
fi
mkdir -p "$INSTALL_DIR/adb-windows"
cd "$INSTALL_DIR/adb-windows"
cp -r "$SOURCES_DIR/$PROJECT_DIR"/. "$INSTALL_DIR/adb-windows/"
mkdir -p "$INSTALL_DIR/$HOST/bin"
cd "$INSTALL_DIR/$HOST/bin"
cp -r "$SOURCES_DIR/$PROJECT_DIR"/. "$INSTALL_DIR/$HOST/bin/"

View File

@ -1,29 +0,0 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
VERSION=35.0.2
FILENAME=platform-tools_r$VERSION-linux.zip
PROJECT_DIR=platform-tools-$VERSION-linux
SHA256SUM=acfdcccb123a8718c46c46c059b2f621140194e5ec1ac9d81715be3d6ab6cd0a
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"/adb
mv "$ZIP_PREFIX"/* .
rmdir "$ZIP_PREFIX"
fi
mkdir -p "$INSTALL_DIR/adb-linux"
cd "$INSTALL_DIR/adb-linux"
cp -r "$SOURCES_DIR/$PROJECT_DIR"/. "$INSTALL_DIR/adb-linux/"

View File

@ -1,29 +0,0 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
VERSION=35.0.2
FILENAME=platform-tools_r$VERSION-darwin.zip
PROJECT_DIR=platform-tools-$VERSION-darwin
SHA256SUM=1820078db90bf21628d257ff052528af1c61bb48f754b3555648f5652fa35d78
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"/adb
mv "$ZIP_PREFIX"/* .
rmdir "$ZIP_PREFIX"
fi
mkdir -p "$INSTALL_DIR/adb-macos"
cd "$INSTALL_DIR/adb-macos"
cp -r "$SOURCES_DIR/$PROJECT_DIR"/. "$INSTALL_DIR/adb-macos/"

View File

@ -1,47 +1,25 @@
#!/usr/bin/env bash
# This file is intended to be sourced by other scripts, not executed
process_args() {
if [[ $# != 3 ]]
then
# <host>: win32 or win64
# <build_type>: native or cross
# <link_type>: static or shared
echo "Syntax: $0 <host> <build_type> <link_type>" >&2
exit 1
fi
if [[ $# != 1 ]]
then
# <host>: win32 or win64
echo "Syntax: $0 <host>" >&2
exit 1
fi
HOST="$1"
BUILD_TYPE="$2" # native or cross
LINK_TYPE="$3" # static or shared
DIRNAME="$HOST-$BUILD_TYPE-$LINK_TYPE"
HOST="$1"
if [[ "$BUILD_TYPE" != native && "$BUILD_TYPE" != cross ]]
then
echo "Unsupported build type (expected native or cross): $BUILD_TYPE" >&2
exit 1
fi
if [[ "$LINK_TYPE" != static && "$LINK_TYPE" != shared ]]
then
echo "Unsupported link type (expected static or shared): $LINK_TYPE" >&2
exit 1
fi
if [[ "$BUILD_TYPE" == cross ]]
then
if [[ "$HOST" = win32 ]]
then
HOST_TRIPLET=i686-w64-mingw32
elif [[ "$HOST" = win64 ]]
then
HOST_TRIPLET=x86_64-w64-mingw32
else
echo "Unsupported cross-build to host: $HOST" >&2
exit 1
fi
fi
}
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"
@ -59,7 +37,7 @@ checksum() {
local file="$1"
local sum="$2"
echo "$file: verifying checksum..."
echo "$sum $file" | shasum -a256 -c
echo "$sum $file" | sha256sum -c
}
get_file() {

View File

@ -3,7 +3,6 @@ set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
process_args "$@"
VERSION=7.1
FILENAME=ffmpeg-$VERSION.tar.xz
@ -23,121 +22,68 @@ fi
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
cd "$BUILD_DIR/$PROJECT_DIR"
if [[ -d "$DIRNAME" ]]
if [[ "$HOST" = win32 ]]
then
echo "'$PWD/$DIRNAME' already exists, not reconfigured"
cd "$DIRNAME"
ARCH=x86
elif [[ "$HOST" = win64 ]]
then
ARCH=x86_64
else
mkdir "$DIRNAME"
cd "$DIRNAME"
echo "Unsupported host: $HOST" >&2
exit 1
fi
if [[ "$HOST" == win* ]]
then
# -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'
elif [[ "$HOST" == "macos" ]]
then
export LDFLAGS="$LDFLAGS -L/opt/homebrew/opt/zlib/lib"
export CPPFLAGS="$CPPFLAGS -I/opt/homebrew/opt/zlib/include"
# -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'
export LDFLAGS="$LDFLAGS-L/opt/homebrew/opt/libiconv/lib"
export CPPFLAGS="$CPPFLAGS -I/opt/homebrew/opt/libiconv/include"
export PKG_CONFIG_PATH="/opt/homebrew/opt/zlib/lib/pkgconfig"
fi
if [[ -d "$HOST" ]]
then
echo "'$PWD/$HOST' already exists, not reconfigured"
cd "$HOST"
else
mkdir "$HOST"
cd "$HOST"
conf=(
--prefix="$INSTALL_DIR/$DIRNAME"
--extra-cflags="-O2 -fPIC"
--disable-programs
--disable-doc
--disable-swscale
--disable-postproc
--disable-avfilter
--disable-network
--disable-everything
"$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
--disable-vaapi
--disable-vdpau
--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
)
if [[ "$HOST" == linux ]]
then
conf+=(
--enable-libv4l2
--enable-outdev=v4l2
--enable-encoder=rawvideo
)
else
# libavdevice is only used for V4L2 on Linux
conf+=(
--disable-avdevice
)
fi
if [[ "$LINK_TYPE" == static ]]
then
conf+=(
--enable-static
--disable-shared
)
else
conf+=(
--disable-static
--enable-shared
)
fi
if [[ "$BUILD_TYPE" == cross ]]
then
conf+=(
--enable-cross-compile
--cross-prefix="${HOST_TRIPLET}-"
--cc="${HOST_TRIPLET}-gcc"
)
case "$HOST" in
win32)
conf+=(
--target-os=mingw32
--arch=x86
)
;;
win64)
conf+=(
--target-os=mingw32
--arch=x86_64
)
;;
*)
echo "Unsupported host: $HOST" >&2
exit 1
esac
fi
"$SOURCES_DIR/$PROJECT_DIR"/configure "${conf[@]}"
fi
make -j

View File

@ -3,7 +3,6 @@ set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
process_args "$@"
VERSION=1.0.27
FILENAME=libusb-$VERSION.tar.gz
@ -26,40 +25,20 @@ cd "$BUILD_DIR/$PROJECT_DIR"
export CFLAGS='-O2'
export CXXFLAGS="$CFLAGS"
if [[ -d "$DIRNAME" ]]
if [[ -d "$HOST" ]]
then
echo "'$PWD/$DIRNAME' already exists, not reconfigured"
cd "$DIRNAME"
echo "'$PWD/$HOST' already exists, not reconfigured"
cd "$HOST"
else
mkdir "$DIRNAME"
cd "$DIRNAME"
conf=(
--prefix="$INSTALL_DIR/$DIRNAME"
)
if [[ "$LINK_TYPE" == static ]]
then
conf+=(
--enable-static
--disable-shared
)
else
conf+=(
--disable-static
--enable-shared
)
fi
if [[ "$BUILD_TYPE" == cross ]]
then
conf+=(
--host="$HOST_TRIPLET"
)
fi
mkdir "$HOST"
cd "$HOST"
"$SOURCES_DIR/$PROJECT_DIR"/bootstrap.sh
"$SOURCES_DIR/$PROJECT_DIR"/configure "${conf[@]}"
"$SOURCES_DIR/$PROJECT_DIR"/configure \
--prefix="$INSTALL_DIR/$HOST" \
--host="$HOST_TRIPLET" \
--enable-shared \
--disable-static
fi
make -j

View File

@ -3,7 +3,6 @@ set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
process_args "$@"
VERSION=2.30.9
FILENAME=SDL-$VERSION.tar.gz
@ -26,54 +25,23 @@ cd "$BUILD_DIR/$PROJECT_DIR"
export CFLAGS='-O2'
export CXXFLAGS="$CFLAGS"
if [[ -d "$DIRNAME" ]]
if [[ -d "$HOST" ]]
then
echo "'$PWD/$HDIRNAME' already exists, not reconfigured"
cd "$DIRNAME"
echo "'$PWD/$HOST' already exists, not reconfigured"
cd "$HOST"
else
mkdir "$DIRNAME"
cd "$DIRNAME"
mkdir "$HOST"
cd "$HOST"
conf=(
--prefix="$INSTALL_DIR/$DIRNAME"
)
if [[ "$HOST" == linux ]]
then
conf+=(
--enable-video-wayland
--enable-video-x11
)
fi
if [[ "$LINK_TYPE" == static ]]
then
conf+=(
--enable-static
--disable-shared
)
else
conf+=(
--disable-static
--enable-shared
)
fi
if [[ "$BUILD_TYPE" == cross ]]
then
conf+=(
--host="$HOST_TRIPLET"
)
fi
"$SOURCES_DIR/$PROJECT_DIR"/configure "${conf[@]}"
"$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
if [[ "$LINK_TYPE" == shared && "$HOST" == win* ]]
then
${HOST_TRIPLET}-strip "$INSTALL_DIR/$DIRNAME/bin/SDL2.dll"
fi
${HOST_TRIPLET}-strip "$INSTALL_DIR/$HOST/bin/SDL2.dll"

View File

@ -46,7 +46,6 @@ src = [
'src/util/acksync.c',
'src/util/audiobuf.c',
'src/util/average.c',
'src/util/env.c',
'src/util/file.c',
'src/util/intmap.c',
'src/util/intr.c',
@ -110,22 +109,20 @@ endif
cc = meson.get_compiler('c')
static = get_option('static')
dependencies = [
dependency('libavformat', version: '>= 57.33', static: static),
dependency('libavcodec', version: '>= 57.37', static: static),
dependency('libavutil', static: static),
dependency('libswresample', static: static),
dependency('sdl2', version: '>= 2.0.5', static: static),
dependency('libavformat', version: '>= 57.33'),
dependency('libavcodec', version: '>= 57.37'),
dependency('libavutil'),
dependency('libswresample'),
dependency('sdl2', version: '>= 2.0.5'),
]
if v4l2_support
dependencies += dependency('libavdevice', static: static)
dependencies += dependency('libavdevice')
endif
if usb_support
dependencies += dependency('libusb-1.0', static: static)
dependencies += dependency('libusb-1.0')
endif
if host_machine.system() == 'windows'
@ -170,6 +167,9 @@ conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
# run a server debugger and wait for a client to be attached
conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
# select the debugger method ('old' for Android < 9, 'new' for Android >= 9)
conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == 'new')
# enable V4L2 support (linux only)
conf.set('HAVE_V4L2', v4l2_support)

View File

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

View File

@ -97,18 +97,6 @@ Select the camera size by its aspect ratio (+/- 10%).
Possible values are "sensor" (use the camera sensor aspect ratio), "\fInum\fR:\fIden\fR" (e.g. "4:3") and "\fIvalue\fR" (e.g. "1.6").
.TP
.BI "\-\-camera\-facing " facing
Select the device camera by its facing direction.
Possible values are "front", "back" and "external".
.TP
.BI "\-\-camera\-fps " fps
Specify the camera capture frame rate.
If not specified, Android's default frame rate (30 fps) is used.
.TP
.B \-\-camera\-high\-speed
Enable high-speed camera capture mode.
@ -121,6 +109,18 @@ Specify the device camera id to mirror.
The available camera ids can be listed by \fB\-\-list\-cameras\fR.
.TP
.BI "\-\-camera\-facing " facing
Select the device camera by its facing direction.
Possible values are "front", "back" and "external".
.TP
.BI "\-\-camera\-fps " fps
Specify the camera capture frame rate.
If not specified, Android's default frame rate (30 fps) is used.
.TP
.BI "\-\-camera\-size " width\fRx\fIheight
Specify an explicit camera capture size.
@ -323,7 +323,8 @@ Create a new display with the specified resolution and density. If not provided,
Examples:
\-\-new\-display=1920x1080
\-\-new\-display=1920x1080/420
\-\-new\-display=1920x1080/420 # force 420 dpi
\-\-new\-display=1920x1080@24 # 24 fps (Android >= 14)
\-\-new\-display # main display size and density
\-\-new\-display=/240 # main display size and 240 dpi

View File

@ -7,7 +7,6 @@
#include "adb_device.h"
#include "adb_parser.h"
#include "util/env.h"
#include "util/file.h"
#include "util/log.h"
#include "util/process_intr.h"
@ -25,45 +24,15 @@
*/
#define SC_ADB_COMMAND(...) { sc_adb_get_executable(), __VA_ARGS__, NULL }
static char *adb_executable;
bool
sc_adb_init(void) {
adb_executable = sc_get_env("ADB");
if (adb_executable) {
LOGD("Using adb: %s", adb_executable);
return true;
}
#if !defined(PORTABLE) || defined(_WIN32)
adb_executable = strdup("adb");
if (!adb_executable) {
LOG_OOM();
return false;
}
#else
// For portable builds, use the absolute path to the adb executable
// in the same directory as scrcpy (except on Windows, where "adb"
// is sufficient)
adb_executable = sc_file_get_local_path("adb");
if (!adb_executable) {
// Error already logged
return false;
}
LOGD("Using adb (portable): %s", adb_executable);
#endif
return true;
}
void
sc_adb_destroy(void) {
free(adb_executable);
}
static const char *adb_executable;
const char *
sc_adb_get_executable(void) {
if (!adb_executable) {
adb_executable = getenv("ADB");
if (!adb_executable)
adb_executable = "adb";
}
return adb_executable;
}
@ -770,21 +739,3 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {
return sc_adb_parse_device_ip(buf);
}
uint16_t
sc_adb_get_device_sdk_version(struct sc_intr *intr, const char *serial) {
char *sdk_version =
sc_adb_getprop(intr, serial, "ro.build.version.sdk", SC_ADB_SILENT);
if (!sdk_version) {
return 0;
}
long value;
bool ok = sc_str_parse_integer(sdk_version, &value);
free(sdk_version);
if (!ok || value < 0 || value > 0xFFFF) {
return 0;
}
return value;
}

View File

@ -15,12 +15,6 @@
#define SC_ADB_SILENT (SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR)
bool
sc_adb_init(void);
void
sc_adb_destroy(void);
const char *
sc_adb_get_executable(void);
@ -120,10 +114,4 @@ sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
char *
sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags);
/**
* Return the device SDK version.
*/
uint16_t
sc_adb_get_device_sdk_version(struct sc_intr *intr, const char *serial);
#endif

View File

@ -255,6 +255,14 @@ static const struct sc_option options[] = {
"ratio), \"<num>:<den>\" (e.g. \"4:3\") or \"<value>\" (e.g. "
"\"1.6\")."
},
{
.longopt_id = OPT_CAMERA_ID,
.longopt = "camera-id",
.argdesc = "id",
.text = "Specify the device camera id to mirror.\n"
"The available camera ids can be listed by:\n"
" scrcpy --list-cameras",
},
{
.longopt_id = OPT_CAMERA_FACING,
.longopt = "camera-facing",
@ -262,14 +270,6 @@ static const struct sc_option options[] = {
.text = "Select the device camera by its facing direction.\n"
"Possible values are \"front\", \"back\" and \"external\".",
},
{
.longopt_id = OPT_CAMERA_FPS,
.longopt = "camera-fps",
.argdesc = "value",
.text = "Specify the camera capture frame rate.\n"
"If not specified, Android's default frame rate (30 fps) is "
"used.",
},
{
.longopt_id = OPT_CAMERA_HIGH_SPEED,
.longopt = "camera-high-speed",
@ -277,14 +277,6 @@ static const struct sc_option options[] = {
"This mode is restricted to specific resolutions and frame "
"rates, listed by --list-camera-sizes.",
},
{
.longopt_id = OPT_CAMERA_ID,
.longopt = "camera-id",
.argdesc = "id",
.text = "Specify the device camera id to mirror.\n"
"The available camera ids can be listed by:\n"
" scrcpy --list-cameras",
},
{
.longopt_id = OPT_CAMERA_SIZE,
.longopt = "camera-size",
@ -292,21 +284,12 @@ static const struct sc_option options[] = {
.text = "Specify an explicit camera capture size.",
},
{
.longopt_id = OPT_CAPTURE_ORIENTATION,
.longopt = "capture-orientation",
.longopt_id = OPT_CAMERA_FPS,
.longopt = "camera-fps",
.argdesc = "value",
.text = "Set the capture video orientation.\n"
"Possible values are 0, 90, 180, 270, flip0, flip90, flip180 "
"and flip270, possibly prefixed by '@'.\n"
"The number represents the clockwise rotation in degrees; the "
"flip\" keyword applies a horizontal flip before the "
"rotation.\n"
"If a leading '@' is passed (@90) for display capture, then "
"the rotation is locked, and is relative to the natural device "
"orientation.\n"
"If '@' is passed alone, then the rotation is locked to the "
"initial device orientation.\n"
"Default is 0.",
.text = "Specify the camera capture frame rate.\n"
"If not specified, Android's default frame rate (30 fps) is "
"used.",
},
{
// Not really deprecated (--codec has never been released), but without
@ -496,6 +479,23 @@ static const struct sc_option options[] = {
.longopt = "list-encoders",
.text = "List video and audio encoders available on the device.",
},
{
.longopt_id = OPT_CAPTURE_ORIENTATION,
.longopt = "capture-orientation",
.argdesc = "value",
.text = "Set the capture video orientation.\n"
"Possible values are 0, 90, 180, 270, flip0, flip90, flip180 "
"and flip270, possibly prefixed by '@'.\n"
"The number represents the clockwise rotation in degrees; the "
"flip\" keyword applies a horizontal flip before the "
"rotation.\n"
"If a leading '@' is passed (@90) for display capture, then "
"the rotation is locked, and is relative to the natural device "
"orientation.\n"
"If '@' is passed alone, then the rotation is locked to the "
"initial device orientation.\n"
"Default is 0.",
},
{
// deprecated
.longopt_id = OPT_LOCK_VIDEO_ORIENTATION,
@ -586,14 +586,17 @@ static const struct sc_option options[] = {
{
.longopt_id = OPT_NEW_DISPLAY,
.longopt = "new-display",
.argdesc = "[<width>x<height>][/<dpi>]",
.argdesc = "[<width>x<height>][/<dpi>][@<fps>]",
.optional_arg = true,
.text = "Create a new display with the specified resolution and "
"density. If not provided, they default to the main display "
"dimensions and DPI.\n"
"From Android 14, it is also possible to request a frame rate. "
"If not provided, it defaults to 60 fps.\n"
"Examples:\n"
" --new-display=1920x1080\n"
" --new-display=1920x1080/420 # force 420 dpi\n"
" --new-display=1920x1080@24 # 24 fps (Android >= 14)\n"
" --new-display # main display size and density\n"
" --new-display=/240 # main display size and 240 dpi",
},

View File

@ -9,7 +9,6 @@
#include "config.h"
#include "compat.h"
#include "util/env.h"
#include "util/file.h"
#include "util/log.h"
#include "util/str.h"
@ -20,22 +19,35 @@
static char *
get_icon_path(void) {
char *icon_path = sc_get_env("SCRCPY_ICON_PATH");
if (icon_path) {
#ifdef __WINDOWS__
const wchar_t *icon_path_env = _wgetenv(L"SCRCPY_ICON_PATH");
#else
const char *icon_path_env = getenv("SCRCPY_ICON_PATH");
#endif
if (icon_path_env) {
// if the envvar is set, use it
#ifdef __WINDOWS__
char *icon_path = sc_str_from_wchars(icon_path_env);
#else
char *icon_path = strdup(icon_path_env);
#endif
if (!icon_path) {
LOG_OOM();
return NULL;
}
LOGD("Using SCRCPY_ICON_PATH: %s", icon_path);
return icon_path;
}
#ifndef PORTABLE
LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH);
icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH);
char *icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH);
if (!icon_path) {
LOG_OOM();
return NULL;
}
#else
icon_path = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME);
char *icon_path = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME);
if (!icon_path) {
LOGE("Could not get icon path");
return NULL;

View File

@ -9,7 +9,6 @@
#include "adb/adb.h"
#include "util/binary.h"
#include "util/env.h"
#include "util/file.h"
#include "util/log.h"
#include "util/net_intr.h"
@ -26,22 +25,35 @@
static char *
get_server_path(void) {
char *server_path = sc_get_env("SCRCPY_SERVER_PATH");
if (server_path) {
#ifdef __WINDOWS__
const wchar_t *server_path_env = _wgetenv(L"SCRCPY_SERVER_PATH");
#else
const char *server_path_env = getenv("SCRCPY_SERVER_PATH");
#endif
if (server_path_env) {
// if the envvar is set, use it
#ifdef __WINDOWS__
char *server_path = sc_str_from_wchars(server_path_env);
#else
char *server_path = strdup(server_path_env);
#endif
if (!server_path) {
LOG_OOM();
return NULL;
}
LOGD("Using SCRCPY_SERVER_PATH: %s", server_path);
return server_path;
}
#ifndef PORTABLE
LOGD("Using server: " SC_SERVER_PATH_DEFAULT);
server_path = strdup(SC_SERVER_PATH_DEFAULT);
char *server_path = strdup(SC_SERVER_PATH_DEFAULT);
if (!server_path) {
LOG_OOM();
return NULL;
}
#else
server_path = sc_file_get_local_path(SC_SERVER_FILENAME);
char *server_path = sc_file_get_local_path(SC_SERVER_FILENAME);
if (!server_path) {
LOGE("Could not get local file path, "
"using " SC_SERVER_FILENAME " from current directory");
@ -189,31 +201,18 @@ execute_server(struct sc_server *server,
cmd[count++] = "app_process";
#ifdef SERVER_DEBUGGER
uint16_t sdk_version = sc_adb_get_device_sdk_version(&server->intr, serial);
if (!sdk_version) {
LOGE("Could not determine SDK version");
return 0;
}
# define SERVER_DEBUGGER_PORT "5005"
const char *dbg;
if (sdk_version < 28) {
// Android < 9
dbg = "-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
SERVER_DEBUGGER_PORT;
} else if (sdk_version < 30) {
// Android >= 9 && Android < 11
dbg = "-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,"
"suspend=y,server=y,address=" SERVER_DEBUGGER_PORT;
} else {
// Android >= 11
// Contrary to the other methods, this does not suspend on start.
// <https://github.com/Genymobile/scrcpy/pull/5466>
dbg = "-XjdwpProvider:adbconnection";
}
cmd[count++] = dbg;
cmd[count++] =
# ifdef SERVER_DEBUGGER_METHOD_NEW
/* Android 9 and above */
"-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y,"
"server=y,address="
# else
/* Android 8 and below */
"-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
# endif
SERVER_DEBUGGER_PORT;
#endif
cmd[count++] = "/"; // unused
cmd[count++] = "com.genymobile.scrcpy.Server";
cmd[count++] = SCRCPY_VERSION;
@ -401,14 +400,10 @@ execute_server(struct sc_server *server,
cmd[count++] = NULL;
#ifdef SERVER_DEBUGGER
LOGI("Server debugger listening%s...",
sdk_version < 30 ? " on port " SERVER_DEBUGGER_PORT : "");
// For Android < 11, from the computer:
// - run `adb forward tcp:5005 tcp:5005`
// For Android >= 11:
// - execute `adb jdwp` to get the jdwp port
// - run `adb forward tcp:5005 jdwp:XXXX` (replace XXXX)
//
LOGI("Server debugger waiting for a client on device port "
SERVER_DEBUGGER_PORT "...");
// From the computer, run
// adb forward tcp:5005 tcp:5005
// Then, from Android Studio: Run > Debug > Edit configurations...
// On the left, click on '+', "Remote", with:
// Host: localhost
@ -485,21 +480,14 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params,
// end of the program
server->params = *params;
bool ok = sc_adb_init();
bool ok = sc_mutex_init(&server->mutex);
if (!ok) {
return false;
}
ok = sc_mutex_init(&server->mutex);
if (!ok) {
sc_adb_destroy();
return false;
}
ok = sc_cond_init(&server->cond_stopped);
if (!ok) {
sc_mutex_destroy(&server->mutex);
sc_adb_destroy();
return false;
}
@ -507,7 +495,6 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params,
if (!ok) {
sc_cond_destroy(&server->cond_stopped);
sc_mutex_destroy(&server->mutex);
sc_adb_destroy();
return false;
}
@ -1149,6 +1136,4 @@ sc_server_destroy(struct sc_server *server) {
sc_intr_destroy(&server->intr);
sc_cond_destroy(&server->cond_stopped);
sc_mutex_destroy(&server->mutex);
sc_adb_destroy();
}

View File

@ -6,9 +6,6 @@
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#ifdef __APPLE__
# include <mach-o/dyld.h> // for _NSGetExecutablePath()
#endif
#include "util/log.h"
@ -63,22 +60,11 @@ sc_file_get_executable_path(void) {
}
buf[len] = '\0';
return strdup(buf);
#elif defined(__APPLE__)
char buf[PATH_MAX];
uint32_t bufsize = PATH_MAX;
if (_NSGetExecutablePath(buf, &bufsize) != 0) {
LOGE("Executable path buffer too small; need %u bytes", bufsize);
return NULL;
}
return realpath(buf, NULL);
#else
// "_" is often used to store the full path of the command being executed
char *path = getenv("_");
if (!path) {
LOGE("Could not determine executable path");
return NULL;
}
return strdup(path);
// in practice, we only need this feature for portable builds, only used on
// Windows, so we don't care implementing it for every platform
// (it's useful to have a working version on Linux for debugging though)
return NULL;
#endif
}

View File

@ -95,14 +95,9 @@ scrcpy_otg(struct scrcpy_options *options) {
// On Windows, only one process could open a USB device
// <https://github.com/Genymobile/scrcpy/issues/2773>
LOGI("Killing adb server (if any)...");
if (sc_adb_init()) {
unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR;
// uninterruptible (intr == NULL), but in practice it's very quick
sc_adb_kill_server(NULL, flags);
sc_adb_destroy();
} else {
LOGW("Could not call adb executable, adb server not killed");
}
unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR;
// uninterruptible (intr == NULL), but in practice it's very quick
sc_adb_kill_server(NULL, flags);
#endif
static const struct sc_usb_callbacks cbs = {

View File

@ -1,29 +0,0 @@
#include "env.h"
#include <stdlib.h>
#include <string.h>
#include "util/str.h"
char *
sc_get_env(const char *varname) {
#ifdef _WIN32
wchar_t *w_varname = sc_str_to_wchars(varname);
if (!w_varname) {
return NULL;
}
const wchar_t *value = _wgetenv(w_varname);
free(w_varname);
if (!value) {
return NULL;
}
return sc_str_from_wchars(value);
#else
const char *value = getenv(varname);
if (!value) {
return NULL;
}
return strdup(value);
#endif
}

View File

@ -1,12 +0,0 @@
#ifndef SC_ENV_H
#define SC_ENV_H
#include "common.h"
// Return the value of the environment variable (may be NULL).
//
// The returned value must be freed by the caller.
char *
sc_get_env(const char *varname);
#endif

View File

@ -9,6 +9,8 @@
#ifdef _WIN32
# include <ws2tcpip.h>
typedef int socklen_t;
typedef SOCKET sc_raw_socket;
# define SC_RAW_SOCKET_NONE INVALID_SOCKET
#else
# include <sys/types.h>
# include <sys/socket.h>
@ -21,6 +23,8 @@
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;
typedef struct in_addr IN_ADDR;
typedef int sc_raw_socket;
# define SC_RAW_SOCKET_NONE -1
#endif
bool
@ -43,26 +47,17 @@ net_cleanup(void) {
#endif
}
static inline bool
sc_raw_socket_close(sc_raw_socket raw_sock) {
#ifndef _WIN32
return !close(raw_sock);
#else
return !closesocket(raw_sock);
#endif
}
static inline sc_socket
wrap(sc_raw_socket sock) {
#ifdef SC_SOCKET_CLOSE_ON_INTERRUPT
if (sock == SC_RAW_SOCKET_NONE) {
#ifdef _WIN32
if (sock == INVALID_SOCKET) {
return SC_SOCKET_NONE;
}
struct sc_socket_wrapper *socket = malloc(sizeof(*socket));
struct sc_socket_windows *socket = malloc(sizeof(*socket));
if (!socket) {
LOG_OOM();
sc_raw_socket_close(sock);
closesocket(sock);
return SC_SOCKET_NONE;
}
@ -77,9 +72,9 @@ wrap(sc_raw_socket sock) {
static inline sc_raw_socket
unwrap(sc_socket socket) {
#ifdef SC_SOCKET_CLOSE_ON_INTERRUPT
#ifdef _WIN32
if (socket == SC_SOCKET_NONE) {
return SC_RAW_SOCKET_NONE;
return INVALID_SOCKET;
}
return socket->socket;
@ -88,6 +83,17 @@ unwrap(sc_socket socket) {
#endif
}
#ifndef HAVE_SOCK_CLOEXEC // avoid unused-function warning
static inline bool
sc_raw_socket_close(sc_raw_socket raw_sock) {
#ifndef _WIN32
return !close(raw_sock);
#else
return !closesocket(raw_sock);
#endif
}
#endif
#ifndef HAVE_SOCK_CLOEXEC
// If SOCK_CLOEXEC does not exist, the flag must be set manually once the
// socket is created
@ -242,9 +248,9 @@ net_interrupt(sc_socket socket) {
sc_raw_socket raw_sock = unwrap(socket);
#ifdef SC_SOCKET_CLOSE_ON_INTERRUPT
#ifdef _WIN32
if (!atomic_flag_test_and_set(&socket->closed)) {
return sc_raw_socket_close(raw_sock);
return !closesocket(raw_sock);
}
return true;
#else
@ -256,15 +262,15 @@ bool
net_close(sc_socket socket) {
sc_raw_socket raw_sock = unwrap(socket);
#ifdef SC_SOCKET_CLOSE_ON_INTERRUPT
#ifdef _WIN32
bool ret = true;
if (!atomic_flag_test_and_set(&socket->closed)) {
ret = sc_raw_socket_close(raw_sock);
ret = !closesocket(raw_sock);
}
free(socket);
return ret;
#else
return sc_raw_socket_close(raw_sock);
return !close(raw_sock);
#endif
}

View File

@ -7,36 +7,21 @@
#include <stdint.h>
#ifdef _WIN32
# include <winsock2.h>
typedef SOCKET sc_raw_socket;
# define SC_RAW_SOCKET_NONE INVALID_SOCKET
#else // not _WIN32
# include <sys/socket.h>
typedef int sc_raw_socket;
# define SC_RAW_SOCKET_NONE -1
#endif
#if defined(_WIN32) || defined(__APPLE__)
// On Windows and macOS, shutdown() does not interrupt accept() or read()
// calls, so net_interrupt() must call close() instead, and net_close() must
// behave accordingly.
// This causes a small race condition (once the socket is closed, its
// handle becomes invalid and may in theory be reassigned before another
// thread calls accept() or read()), but it is deemed acceptable as a
// workaround.
# define SC_SOCKET_CLOSE_ON_INTERRUPT
#endif
#ifdef SC_SOCKET_CLOSE_ON_INTERRUPT
# include <stdatomic.h>
# define SC_SOCKET_NONE NULL
typedef struct sc_socket_wrapper {
sc_raw_socket socket;
typedef struct sc_socket_windows {
SOCKET socket;
atomic_flag closed;
} *sc_socket;
#else
#else // not _WIN32
# include <sys/socket.h>
# define SC_SOCKET_NONE -1
typedef sc_raw_socket sc_socket;
typedef int sc_socket;
#endif
#define IPV4_LOCALHOST 0x7F000001

View File

@ -77,7 +77,7 @@ pip3 install meson
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
# client build dependencies
sudo dnf install SDL2-devel ffms2-devel libusb1-devel libavdevice-free-devel meson gcc make
sudo dnf install SDL2-devel ffms2-devel libusb1-devel meson gcc make
# server build dependencies
sudo dnf install java-devel
@ -233,10 +233,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server
- [`scrcpy-server-v3.0`][direct-scrcpy-server]
<sub>SHA-256: `800044c62a94d5fc16f5ab9c86d45b1050eae3eb436514d1b0d2fe2646b894ea`</sub>
- [`scrcpy-server-v2.7`][direct-scrcpy-server]
<sub>SHA-256: `a23c5659f36c260f105c022d27bcb3eafffa26070e7baa9eda66d01377a1adba`</sub>
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-server-v3.0
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-server-v2.7
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:

View File

@ -23,20 +23,14 @@ To control the device without mirroring:
scrcpy --no-video --no-audio
```
By default, the mouse is disabled when video playback is turned off.
To control the device using a relative mouse, enable UHID mouse mode:
```bash
scrcpy --no-video --no-audio --mouse=uhid
scrcpy --no-video --no-audio -M # short version
```
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 --mouse=uhid --keyboard=uhid
scrcpy --no-video --no-audio -MK # short version
scrcpy --no-video --no-audio --keyboard=uhid
scrcpy --no-video --no-audio -K # short version
```
To use AOA instead (over USB only):

View File

@ -461,30 +461,26 @@ meson setup x -Dserver_debugger=true
meson configure x -Dserver_debugger=true
```
Then recompile, and run scrcpy.
If your device runs Android 8 or below, set the `server_debugger_method` to
`old` in addition:
For Android < 11, it will start a debugger on port 5005 on the device and wait:
```bash
meson setup x -Dserver_debugger=true -Dserver_debugger_method=old
# or, if x is already configured
meson configure x -Dserver_debugger=true -Dserver_debugger_method=old
```
Then recompile.
When you start scrcpy, it will start a debugger on port 5005 on the device.
Redirect that port to the computer:
```bash
adb forward tcp:5005 tcp:5005
```
For Android >= 11, first find the listening port:
```bash
adb jdwp
# press Ctrl+C to interrupt
```
Then redirect the resulting PID:
```bash
adb forward tcp:5005 jdwp:XXXX # replace XXXX
```
In Android Studio, _Run_ > _Debug_ > _Edit configurations..._ On the left, click
on `+`, _Remote_, and fill the form:
In Android Studio, _Run_ > _Debug_ > _Edit configurations..._ On the left, click on
`+`, _Remote_, and fill the form:
- Host: `localhost`
- Port: `5005`

View File

@ -2,23 +2,6 @@
## Install
### From the official release
Download a static build of the [latest release]:
- [`scrcpy-linux-v3.0.tar.gz`][direct-linux] (x86_64)
<sub>SHA-256: `06cb74e22f758228c944cea048b78e42b2925c2affe2b5aca901cfd6a649e503`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-linux]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-linux-v3.0.tar.gz
and extract it.
_Static builds of scrcpy for Linux are still experimental._
### From your package manager
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
Scrcpy is packaged in several distributions and package managers:
@ -30,10 +13,10 @@ Scrcpy is packaged in several distributions and package managers:
- Snap: `snap install scrcpy`
- … (see [repology](https://repology.org/project/scrcpy/versions))
### Latest version
### From an install script
To install the latest release from `master`, follow this simplified process.
However, the packaged version is not always the latest release. To install the
latest release from `master`, follow this simplified process.
First, you need to install the required packages:

View File

@ -2,23 +2,6 @@
## Install
### From the official release
Download a static build of the [latest release]:
- [`scrcpy-macos-v3.0.tar.gz`][direct-macos] (arm64)
<sub>SHA-256: `5db9821918537eb3aaf0333cdd05baf85babdd851972d5f1b71f86da0530b4bf`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-macos]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-macos-v3.0.tar.gz
and extract it.
_Static builds of scrcpy for macOS are still experimental._
### From a package manager
Scrcpy is available in [Homebrew]:
```bash
@ -30,7 +13,7 @@ brew install scrcpy
You need `adb`, accessible from your `PATH`. If you don't have it yet:
```bash
brew install --cask android-platform-tools
brew install android-platform-tools
```
Alternatively, Scrcpy is also available in [MacPorts], which sets up `adb` for you:

View File

@ -96,7 +96,7 @@ Sometimes, the default encoder may have issues or even crash, so it is useful to
try another one:
```bash
scrcpy --video-codec=h264 --video-encoder=OMX.qcom.video.encoder.avc
scrcpy --video-codec=h264 --video-encoder='OMX.qcom.video.encoder.avc'
```

View File

@ -7,6 +7,7 @@ To mirror a new virtual display instead of the device screen:
```bash
scrcpy --new-display=1920x1080
scrcpy --new-display=1920x1080/420 # force 420 dpi
scrcpy --new-display=1920x1080@24 # 24 fps (Android >= 14)
scrcpy --new-display # use the main display size and density
scrcpy --new-display=/240 # use the main display size and 240 dpi
```
@ -15,10 +16,8 @@ scrcpy --new-display=/240 # use the main display size and 240 dpi
On some devices, a launcher is available in the virtual display.
When no launcher is available (or if is explicitly disabled by
[`--no-vd-system-decorations`](#system-decorations)), the virtual display is
empty. In that case, you must [start an Android
app](device.md#start-android-app).
When no launcher is available, the virtual display is empty. In that case, you
must [start an Android app](device.md#start-android-app).
For example:
@ -26,27 +25,12 @@ For example:
scrcpy --new-display=1920x1080 --start-app=org.videolan.vlc
```
The app may itself be a launcher. For example, to run the open source [Fossify
Launcher]:
```bash
scrcpy --new-display=1920x1080 --no-vd-system-decorations --start-app=org.fossify.home
```
[Fossify Launcher]: https://f-droid.org/en/packages/org.fossify.home/
## System decorations
By default, virtual display system decorations are enabled. To disable them, use
`--no-vd-system-decorations`:
By default, virtual display system decorations are enabled. But some devices
might display a broken UI;
```
scrcpy --new-display --no-vd-system-decorations
```
This is useful for some devices which might display a broken UI, or to disable
any default launcher UI available in virtual displays.
Use `--no-vd-system-decorations` to disable it.
Note that if no app is started, no content will be rendered, so no video frame
will be produced at all.

View File

@ -2,32 +2,27 @@
## Install
### From the official release
Download the [latest release]:
- [`scrcpy-win64-v3.0.zip`][direct-win64] (64-bit)
<sub>SHA-256: `dfbe8a8fef6535197acc506936bfd59d0aa0427e9b44fb2e5c550eae642f72be`</sub>
- [`scrcpy-win32-v3.0.zip`][direct-win32] (32-bit)
<sub>SHA-256: `7cbf8d7a6ebfdca7b3b161e29a481c11088305f3e0a89d28e8e62f70c7bd0028`</sub>
- [`scrcpy-win64-v2.7.zip`][direct-win64] (64-bit)
<sub>SHA-256: `5910bc18d5a16f42d84185ddc7e16a4cee6a6f5f33451559c1a1d6d0099bd5f5`</sub>
- [`scrcpy-win32-v2.7.zip`][direct-win32] (32-bit)
<sub>SHA-256: `ef4daf89d500f33d78b830625536ecb18481429dd94433e7634c824292059d06`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-win64-v3.0.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-win32-v3.0.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-win64-v2.7.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-win32-v2.7.zip
and extract it.
### From a package manager
From [Chocolatey]:
Alternatively, you could install it from packages manager, like [Chocolatey]:
```bash
choco install scrcpy
choco install adb # if you don't have it yet
```
From [Scoop]:
or [Scoop]:
```bash
@ -35,6 +30,7 @@ scoop install scrcpy
scoop install adb # if you don't have it yet
```
[Winget]: https://github.com/microsoft/winget-cli
[Chocolatey]: https://chocolatey.org/
[Scoop]: https://scoop.sh

View File

@ -2,8 +2,8 @@
set -e
BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-server-v3.0
PREBUILT_SERVER_SHA256=800044c62a94d5fc16f5ab9c86d45b1050eae3eb436514d1b0d2fe2646b894ea
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-server-v2.7
PREBUILT_SERVER_SHA256=a23c5659f36c260f105c022d27bcb3eafffa26070e7baa9eda66d01377a1adba
echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

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

View File

@ -2,7 +2,7 @@ option('compile_app', type: 'boolean', value: true, description: 'Build the clie
option('compile_server', type: 'boolean', value: true, description: 'Build the server')
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable')
option('static', type: 'boolean', value: false, description: 'Use static dependencies')
option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached')
option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")')
option('v4l2', type: 'boolean', value: true, description: 'Enable V4L2 feature when supported')
option('usb', type: 'boolean', value: true, description: 'Enable HID/OTG features when supported')

141
release.mk Normal file
View File

@ -0,0 +1,141 @@
# This makefile provides recipes to build a "portable" version of scrcpy for
# Windows.
#
# Here, "portable" means that the client and server binaries are expected to be
# anywhere, but in the same directory, instead of well-defined separate
# locations (e.g. /usr/bin/scrcpy and /usr/share/scrcpy/scrcpy-server).
#
# In particular, this implies to change the location from where the client push
# the server to the device.
.PHONY: default clean \
test test-client test-server \
build-server \
prepare-deps-win32 prepare-deps-win64 \
build-win32 build-win64 \
zip-win32 zip-win64 \
package release
GRADLE ?= ./gradlew
TEST_BUILD_DIR := build-test
SERVER_BUILD_DIR := build-server
WIN32_BUILD_DIR := build-win32
WIN64_BUILD_DIR := build-win64
VERSION ?= $(shell git describe --tags --exclude='*install-release' --always)
ZIP := zip
WIN32_TARGET_DIR := scrcpy-win32-$(VERSION)
WIN64_TARGET_DIR := scrcpy-win64-$(VERSION)
WIN32_TARGET := $(WIN32_TARGET_DIR).zip
WIN64_TARGET := $(WIN64_TARGET_DIR).zip
RELEASE_DIR := release-$(VERSION)
release: clean test build-server build-win32 build-win64 package
clean:
$(GRADLE) clean
rm -rf "$(ZIP)" "$(TEST_BUILD_DIR)" "$(SERVER_BUILD_DIR)" \
"$(WIN32_BUILD_DIR)" "$(WIN64_BUILD_DIR)"
test-client:
[ -d "$(TEST_BUILD_DIR)" ] || ( mkdir "$(TEST_BUILD_DIR)" && \
meson setup "$(TEST_BUILD_DIR)" -Db_sanitize=address )
ninja -C "$(TEST_BUILD_DIR)"
test-server:
$(GRADLE) -p server check
test: test-client test-server
build-server:
$(GRADLE) -p server assembleRelease
mkdir -p "$(SERVER_BUILD_DIR)/server"
cp server/build/outputs/apk/release/server-release-unsigned.apk \
"$(SERVER_BUILD_DIR)/server/scrcpy-server"
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-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
rm -rf "$(WIN32_BUILD_DIR)"
mkdir -p "$(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" \
--cross-file=cross_win32.txt \
--buildtype=release --strip -Db_lto=true \
-Dcompile_server=false \
-Dportable=true
ninja -C "$(WIN32_BUILD_DIR)"
# Group intermediate outputs into a 'dist' directory
mkdir -p "$(WIN32_BUILD_DIR)/dist"
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(WIN32_BUILD_DIR)/dist/"
cp app/data/scrcpy-console.bat "$(WIN32_BUILD_DIR)/dist/"
cp app/data/scrcpy-noconsole.vbs "$(WIN32_BUILD_DIR)/dist/"
cp app/data/icon.png "$(WIN32_BUILD_DIR)/dist/"
cp app/data/open_a_terminal_here.bat "$(WIN32_BUILD_DIR)/dist/"
cp app/deps/work/install/win32/bin/*.dll "$(WIN32_BUILD_DIR)/dist/"
cp app/deps/work/install/win32/bin/adb.exe "$(WIN32_BUILD_DIR)/dist/"
build-win64: prepare-deps-win64
rm -rf "$(WIN64_BUILD_DIR)"
mkdir -p "$(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" \
--cross-file=cross_win64.txt \
--buildtype=release --strip -Db_lto=true \
-Dcompile_server=false \
-Dportable=true
ninja -C "$(WIN64_BUILD_DIR)"
# Group intermediate outputs into a 'dist' directory
mkdir -p "$(WIN64_BUILD_DIR)/dist"
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(WIN64_BUILD_DIR)/dist/"
cp app/data/scrcpy-console.bat "$(WIN64_BUILD_DIR)/dist/"
cp app/data/scrcpy-noconsole.vbs "$(WIN64_BUILD_DIR)/dist/"
cp app/data/icon.png "$(WIN64_BUILD_DIR)/dist/"
cp app/data/open_a_terminal_here.bat "$(WIN64_BUILD_DIR)/dist/"
cp app/deps/work/install/win64/bin/*.dll "$(WIN64_BUILD_DIR)/dist/"
cp app/deps/work/install/win64/bin/adb.exe "$(WIN64_BUILD_DIR)/dist/"
zip-win32:
mkdir -p "$(ZIP)/$(WIN32_TARGET_DIR)"
cp -r "$(WIN32_BUILD_DIR)/dist/." "$(ZIP)/$(WIN32_TARGET_DIR)/"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(ZIP)/$(WIN32_TARGET_DIR)/"
cd "$(ZIP)"; \
zip -r "$(WIN32_TARGET)" "$(WIN32_TARGET_DIR)"
rm -rf "$(ZIP)/$(WIN32_TARGET_DIR)"
zip-win64:
mkdir -p "$(ZIP)/$(WIN64_TARGET_DIR)"
cp -r "$(WIN64_BUILD_DIR)/dist/." "$(ZIP)/$(WIN64_TARGET_DIR)/"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(ZIP)/$(WIN64_TARGET_DIR)/"
cd "$(ZIP)"; \
zip -r "$(WIN64_TARGET)" "$(WIN64_TARGET_DIR)"
rm -rf "$(ZIP)/$(WIN64_TARGET_DIR)"
package: zip-win32 zip-win64
mkdir -p "$(RELEASE_DIR)"
cp "$(SERVER_BUILD_DIR)/server/scrcpy-server" \
"$(RELEASE_DIR)/scrcpy-server-$(VERSION)"
cp "$(ZIP)/$(WIN32_TARGET)" "$(RELEASE_DIR)"
cp "$(ZIP)/$(WIN64_TARGET)" "$(RELEASE_DIR)"
cd "$(RELEASE_DIR)" && \
sha256sum "scrcpy-server-$(VERSION)" \
"scrcpy-win32-$(VERSION).zip" \
"scrcpy-win64-$(VERSION).zip" > SHA256SUMS.txt
@echo "Release generated in $(RELEASE_DIR)/"

2
release.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
make -f release.mk

2
release/.gitignore vendored
View File

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

View File

@ -1,5 +0,0 @@
# This file must be sourced from the release scripts directory
WORK_DIR="$PWD/work"
OUTPUT_DIR="$PWD/output"
VERSION="${VERSION:-$(git describe --tags --always)}"

View File

@ -1,42 +0,0 @@
#!/bin/bash
set -ex
cd "$(dirname ${BASH_SOURCE[0]})"
. build_common
cd .. # root project dir
if [[ $# != 1 ]]
then
echo "Syntax: $0 <arch>" >&2
exit 1
fi
ARCH="$1"
LINUX_BUILD_DIR="$WORK_DIR/build-linux-$ARCH"
app/deps/adb_linux.sh
app/deps/sdl.sh linux native static
app/deps/ffmpeg.sh linux native static
app/deps/libusb.sh linux native static
DEPS_INSTALL_DIR="$PWD/app/deps/work/install/linux-native-static"
ADB_INSTALL_DIR="$PWD/app/deps/work/install/adb-linux"
rm -rf "$LINUX_BUILD_DIR"
meson setup "$LINUX_BUILD_DIR" \
--pkg-config-path="$DEPS_INSTALL_DIR/lib/pkgconfig" \
-Dc_args="-I$DEPS_INSTALL_DIR/include" \
-Dc_link_args="-L$DEPS_INSTALL_DIR/lib" \
--buildtype=release \
--strip \
-Db_lto=true \
-Dcompile_server=false \
-Dportable=true \
-Dstatic=true
ninja -C "$LINUX_BUILD_DIR"
# Group intermediate outputs into a 'dist' directory
mkdir -p "$LINUX_BUILD_DIR/dist"
cp "$LINUX_BUILD_DIR"/app/scrcpy "$LINUX_BUILD_DIR/dist/"
cp app/data/icon.png "$LINUX_BUILD_DIR/dist/"
cp app/scrcpy.1 "$LINUX_BUILD_DIR/dist/"
cp -r "$ADB_INSTALL_DIR"/. "$LINUX_BUILD_DIR/dist/"

View File

@ -1,42 +0,0 @@
#!/bin/bash
set -ex
cd "$(dirname ${BASH_SOURCE[0]})"
. build_common
cd .. # root project dir
if [[ $# != 1 ]]
then
echo "Syntax: $0 <arch>" >&2
exit 1
fi
ARCH="$1"
MACOS_BUILD_DIR="$WORK_DIR/build-macos-$ARCH"
app/deps/adb_macos.sh
app/deps/sdl.sh macos native static
app/deps/ffmpeg.sh macos native static
app/deps/libusb.sh macos native static
DEPS_INSTALL_DIR="$PWD/app/deps/work/install/macos-native-static"
ADB_INSTALL_DIR="$PWD/app/deps/work/install/adb-macos"
rm -rf "$MACOS_BUILD_DIR"
meson setup "$MACOS_BUILD_DIR" \
--pkg-config-path="$DEPS_INSTALL_DIR/lib/pkgconfig" \
-Dc_args="-I$DEPS_INSTALL_DIR/include" \
-Dc_link_args="-L$DEPS_INSTALL_DIR/lib" \
--buildtype=release \
--strip \
-Db_lto=true \
-Dcompile_server=false \
-Dportable=true \
-Dstatic=true
ninja -C "$MACOS_BUILD_DIR"
# Group intermediate outputs into a 'dist' directory
mkdir -p "$MACOS_BUILD_DIR/dist"
cp "$MACOS_BUILD_DIR"/app/scrcpy "$MACOS_BUILD_DIR/dist/"
cp app/data/icon.png "$MACOS_BUILD_DIR/dist/"
cp app/scrcpy.1 "$MACOS_BUILD_DIR/dist/"
cp -r "$ADB_INSTALL_DIR"/. "$MACOS_BUILD_DIR/dist/"

View File

@ -1,14 +0,0 @@
#!/bin/bash
set -ex
cd "$(dirname ${BASH_SOURCE[0]})"
. build_common
cd .. # root project dir
GRADLE="${GRADLE:-./gradlew}"
SERVER_BUILD_DIR="$WORK_DIR/build-server"
rm -rf "$SERVER_BUILD_DIR"
"$GRADLE" -p server assembleRelease
mkdir -p "$SERVER_BUILD_DIR/server"
cp server/build/outputs/apk/release/server-release-unsigned.apk \
"$SERVER_BUILD_DIR/server/scrcpy-server"

View File

@ -1,52 +0,0 @@
#!/bin/bash
set -ex
case "$1" in
32)
WINXX=win32
;;
64)
WINXX=win64
;;
*)
echo "ERROR: $0 must be called with one argument: 32 or 64" >&2
exit 1
;;
esac
cd "$(dirname ${BASH_SOURCE[0]})"
. build_common
cd .. # root project dir
WINXX_BUILD_DIR="$WORK_DIR/build-$WINXX"
app/deps/adb_windows.sh
app/deps/sdl.sh $WINXX cross shared
app/deps/ffmpeg.sh $WINXX cross shared
app/deps/libusb.sh $WINXX cross shared
DEPS_INSTALL_DIR="$PWD/app/deps/work/install/$WINXX-cross-shared"
ADB_INSTALL_DIR="$PWD/app/deps/work/install/adb-windows"
rm -rf "$WINXX_BUILD_DIR"
meson setup "$WINXX_BUILD_DIR" \
--pkg-config-path="$DEPS_INSTALL_DIR/lib/pkgconfig" \
-Dc_args="-I$DEPS_INSTALL_DIR/include" \
-Dc_link_args="-L$DEPS_INSTALL_DIR/lib" \
--cross-file=cross_$WINXX.txt \
--buildtype=release \
--strip \
-Db_lto=true \
-Dcompile_server=false \
-Dportable=true
ninja -C "$WINXX_BUILD_DIR"
# Group intermediate outputs into a 'dist' directory
mkdir -p "$WINXX_BUILD_DIR/dist"
cp "$WINXX_BUILD_DIR"/app/scrcpy.exe "$WINXX_BUILD_DIR/dist/"
cp app/data/scrcpy-console.bat "$WINXX_BUILD_DIR/dist/"
cp app/data/scrcpy-noconsole.vbs "$WINXX_BUILD_DIR/dist/"
cp app/data/icon.png "$WINXX_BUILD_DIR/dist/"
cp app/data/open_a_terminal_here.bat "$WINXX_BUILD_DIR/dist/"
cp "$DEPS_INSTALL_DIR"/bin/*.dll "$WINXX_BUILD_DIR/dist/"
cp -r "$ADB_INSTALL_DIR"/. "$WINXX_BUILD_DIR/dist/"

View File

@ -1,14 +0,0 @@
#!/bin/bash
set -ex
cd "$(dirname ${BASH_SOURCE[0]})"
. build_common
cd "$OUTPUT_DIR"
sha256sum "scrcpy-server-$VERSION" \
"scrcpy-linux-x86_64-$VERSION.tar.gz" \
"scrcpy-win32-$VERSION.zip" \
"scrcpy-win64-$VERSION.zip" \
"scrcpy-macos-aarch64-$VERSION.tar.gz" \
"scrcpy-macos-x86_64-$VERSION.tar.gz" \
| tee SHA256SUMS.txt
echo "Release checksums generated in $PWD/SHA256SUMS.txt"

View File

@ -1,52 +0,0 @@
#!/bin/bash
set -ex
cd "$(dirname ${BASH_SOURCE[0]})"
. build_common
cd .. # root project dir
if [[ $# != 2 ]]
then
# <target_name>: for example win64
# <format>: zip or tar.gz
echo "Syntax: $0 <target> <format>" >&2
exit 1
fi
FORMAT=$2
if [[ "$FORMAT" != zip && "$FORMAT" != tar.gz ]]
then
echo "Invalid format (expected zip or tar.gz): $FORMAT" >&2
exit 1
fi
BUILD_DIR="$WORK_DIR/build-$1"
ARCHIVE_DIR="$BUILD_DIR/release-archive"
TARGET_DIRNAME="scrcpy-$1-$VERSION"
rm -rf "$ARCHIVE_DIR/$TARGET_DIRNAME"
mkdir -p "$ARCHIVE_DIR/$TARGET_DIRNAME"
cp -r "$BUILD_DIR/dist/." "$ARCHIVE_DIR/$TARGET_DIRNAME/"
cp "$WORK_DIR/build-server/server/scrcpy-server" "$ARCHIVE_DIR/$TARGET_DIRNAME/"
mkdir -p "$OUTPUT_DIR"
cd "$ARCHIVE_DIR"
rm -f "$OUTPUT_DIR/$TARGET_DIRNAME.$FORMAT"
case "$FORMAT" in
zip)
zip -r "$OUTPUT_DIR/$TARGET_DIRNAME.zip" "$TARGET_DIRNAME"
;;
tar.gz)
tar cvzf "$OUTPUT_DIR/$TARGET_DIRNAME.tar.gz" "$TARGET_DIRNAME"
;;
*)
echo "Invalid format (expected zip or tar.gz): $FORMAT" >&2
exit 1
esac
rm -rf "$TARGET_DIRNAME"
cd -
echo "Generated '$OUTPUT_DIR/$TARGET_DIRNAME.$FORMAT'"

View File

@ -1,10 +0,0 @@
#!/bin/bash
set -ex
cd "$(dirname ${BASH_SOURCE[0]})"
OUTPUT_DIR="$PWD/output"
. build_common
cd .. # root project dir
mkdir -p "$OUTPUT_DIR"
cp "$WORK_DIR/build-server/server/scrcpy-server" "$OUTPUT_DIR/scrcpy-server-$VERSION"
echo "Generated '$OUTPUT_DIR/scrcpy-server-$VERSION'"

View File

@ -1,24 +0,0 @@
#!/bin/bash
# To customize the version name:
# VERSION=myversion ./release.sh
set -e
cd "$(dirname ${BASH_SOURCE[0]})"
rm -rf output
./test_server.sh
./test_client.sh
./build_server.sh
./build_windows.sh 32
./build_windows.sh 64
./build_linux.sh x86_64
./package_server.sh
./package_client.sh win32 zip
./package_client.sh win64 zip
./package_client.sh linux-x86_64 tar.gz
./generate_checksums.sh
echo "Release generated in $PWD/output"

View File

@ -1,12 +0,0 @@
#!/bin/bash
set -ex
cd "$(dirname ${BASH_SOURCE[0]})"
. build_common
cd .. # root project dir
TEST_BUILD_DIR="$WORK_DIR/build-test"
rm -rf "$TEST_BUILD_DIR"
meson setup "$TEST_BUILD_DIR" -Dcompile_server=false \
-Db_sanitize=address,undefined
ninja -C "$TEST_BUILD_DIR" test

View File

@ -1,9 +0,0 @@
#!/bin/bash
set -ex
cd "$(dirname ${BASH_SOURCE[0]})"
. build_common
cd .. # root project dir
GRADLE="${GRADLE:-./gradlew}"
"$GRADLE" -p server check

View File

@ -7,8 +7,8 @@ android {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 35
versionCode 30000
versionName "3.0"
versionCode 20700
versionName "2.7"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@ -12,11 +12,10 @@
set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=3.0
SCRCPY_VERSION_NAME=2.7
PLATFORM=${ANDROID_PLATFORM:-35}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0}
PLATFORM_TOOLS="$ANDROID_HOME/platforms/android-$PLATFORM"
BUILD_TOOLS_DIR="$ANDROID_HOME/build-tools/$BUILD_TOOLS"
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
@ -24,8 +23,7 @@ CLASSES_DIR="$BUILD_DIR/classes"
GEN_DIR="$BUILD_DIR/gen"
SERVER_DIR=$(dirname "$0")
SERVER_BINARY=scrcpy-server
ANDROID_JAR="$PLATFORM_TOOLS/android.jar"
ANDROID_AIDL="$PLATFORM_TOOLS/framework.aidl"
ANDROID_JAR="$ANDROID_HOME/platforms/android-$PLATFORM/android.jar"
LAMBDA_JAR="$BUILD_TOOLS_DIR/core-lambda-stubs.jar"
echo "Platform: android-$PLATFORM"
@ -51,8 +49,6 @@ cd "$SERVER_DIR/src/main/aidl"
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. \
android/content/IOnPrimaryClipChangedListener.aidl
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. android/view/IDisplayFoldListener.aidl
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. -p "$ANDROID_AIDL" \
android/view/IDisplayWindowListener.aidl
# Fake sources to expose hidden Android types to the project
FAKE_SRC=( \

View File

@ -207,15 +207,13 @@ public final class CleanUp {
}
}
// Change the power of the main display when mirroring a virtual display
int targetDisplayId = displayId != Device.DISPLAY_ID_NONE ? displayId : 0;
if (Device.isScreenOn(targetDisplayId)) {
if (displayId != Device.DISPLAY_ID_NONE && Device.isScreenOn(displayId)) {
if (powerOffScreen) {
Ln.i("Power off screen");
Device.powerOffScreen(targetDisplayId);
Device.powerOffScreen(displayId);
} else if (restoreDisplayPower) {
Ln.i("Restoring display power");
Device.setDisplayPower(targetDisplayId, true);
Device.setDisplayPower(displayId, true);
}
}

View File

@ -566,36 +566,68 @@ public class Options {
}
}
private static NewDisplay parseNewDisplay(String newDisplay) {
// Possible inputs:
// - "" (empty string)
// - "<width>x<height>/<dpi>"
// - "<width>x<height>"
// - "/<dpi>"
static NewDisplay parseNewDisplay(String newDisplay) {
// Input in the form "[<width>x<height>][/<dpi>][@<fps>]" (each [] block is optional)
// For convenience, the order of dpi and fps does not matter.
if (newDisplay.isEmpty()) {
return new NewDisplay();
}
String[] tokens = newDisplay.split("/");
String sizeString = null;
String dpiString = null;
String fpsString = null;
Size size;
if (!tokens[0].isEmpty()) {
size = parseSize(tokens[0]);
} else {
size = null;
}
int dpi;
if (tokens.length >= 2) {
dpi = Integer.parseInt(tokens[1]);
if (dpi <= 0) {
throw new IllegalArgumentException("Invalid non-positive dpi: " + tokens[1]);
String s = newDisplay;
while (true) {
int slashIndex = s.indexOf('/');
int atIndex = s.indexOf('@');
int lastSepIndex = Math.max(slashIndex, atIndex);
if (lastSepIndex == -1) {
if (!s.isEmpty()) {
sizeString = s;
}
break;
} else {
char lastSep = newDisplay.charAt(lastSepIndex);
if (lastSep == '@') {
if (fpsString != null) {
throw new IllegalArgumentException("Invalid new display format: '@' may not appear twice");
}
fpsString = s.substring(lastSepIndex + 1);
} else {
assert lastSep == '/';
if (dpiString != null) {
throw new IllegalArgumentException("Invalid new display format: '/' may not appear twice");
}
dpiString = s.substring(lastSepIndex + 1);
}
s = s.substring(0, lastSepIndex);
}
} else {
dpi = 0;
}
return new NewDisplay(size, dpi);
Size size = null;
int dpi = 0;
float fps = 0;
if (sizeString != null) {
size = parseSize(sizeString);
}
if (dpiString != null) {
dpi = Integer.parseInt(dpiString);
if (dpi <= 0) {
throw new IllegalArgumentException("Invalid non-positive dpi: " + dpiString);
}
}
if (fpsString != null) {
fps = Float.parseFloat(fpsString);
if (fps < 0) {
throw new IllegalArgumentException("Invalid negative fps: " + fpsString);
}
}
return new NewDisplay(size, dpi, fps);
}
private static Pair<Orientation.Lock, Orientation> parseCaptureOrientation(String value) {

View File

@ -21,7 +21,6 @@ import android.content.IOnPrimaryClipChangedListener;
import android.content.Intent;
import android.os.Build;
import android.os.SystemClock;
import android.util.Pair;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@ -282,7 +281,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
setClipboard(msg.getText(), msg.getPaste(), msg.getSequence());
break;
case ControlMessage.TYPE_SET_DISPLAY_POWER:
if (supportsInputEvents) {
if (supportsInputEvents && displayId != Device.DISPLAY_ID_NONE) {
setDisplayPower(msg.getOn());
}
break;
@ -351,47 +350,24 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
return successCount;
}
private Pair<Point, Integer> getEventPointAndDisplayId(Position position) {
// it hides the field on purpose, to read it with atomic access
@SuppressWarnings("checkstyle:HiddenField")
DisplayData displayData = this.displayData.get();
// In scrcpy, displayData should never be null (a touch event can only be generated from the client when a video frame is present).
// However, it is possible to send events without video playback when using scrcpy-server alone (except for virtual displays).
assert displayData != null || displayId != Device.DISPLAY_ID_NONE : "Cannot receive a positional event without a display";
Point point;
int targetDisplayId;
if (displayData != null) {
point = displayData.positionMapper.map(position);
if (point == null) {
if (Ln.isEnabled(Ln.Level.VERBOSE)) {
Size eventSize = position.getScreenSize();
Size currentSize = displayData.positionMapper.getVideoSize();
Ln.v("Ignore positional event generated for size " + eventSize + " (current size is " + currentSize + ")");
}
return null;
}
targetDisplayId = displayData.virtualDisplayId;
} else {
// No display, use the raw coordinates
point = position.getPoint();
targetDisplayId = displayId;
}
return Pair.create(point, targetDisplayId);
}
private boolean injectTouch(int action, long pointerId, Position position, float pressure, int actionButton, int buttons) {
long now = SystemClock.uptimeMillis();
Pair<Point, Integer> pair = getEventPointAndDisplayId(position);
if (pair == null) {
// it hides the field on purpose, to read it with atomic access
@SuppressWarnings("checkstyle:HiddenField")
DisplayData displayData = this.displayData.get();
assert displayData != null : "Cannot receive a touch event without a display";
Point point = displayData.positionMapper.map(position);
if (point == null) {
if (Ln.isEnabled(Ln.Level.VERBOSE)) {
Size eventSize = position.getScreenSize();
Size currentSize = displayData.positionMapper.getVideoSize();
Ln.v("Ignore touch event generated for size " + eventSize + " (current size is " + currentSize + ")");
}
return false;
}
Point point = pair.first;
int targetDisplayId = pair.second;
int pointerIndex = pointersState.getPointerIndex(pointerId);
if (pointerIndex == -1) {
Ln.w("Too many pointers for touch event");
@ -445,7 +421,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
// First button pressed: ACTION_DOWN
MotionEvent downEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_DOWN, pointerCount, pointerProperties,
pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0);
if (!Device.injectEvent(downEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) {
if (!Device.injectEvent(downEvent, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) {
return false;
}
}
@ -456,7 +432,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
if (!InputManager.setActionButton(pressEvent, actionButton)) {
return false;
}
if (!Device.injectEvent(pressEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) {
if (!Device.injectEvent(pressEvent, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) {
return false;
}
@ -470,7 +446,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
if (!InputManager.setActionButton(releaseEvent, actionButton)) {
return false;
}
if (!Device.injectEvent(releaseEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) {
if (!Device.injectEvent(releaseEvent, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) {
return false;
}
@ -478,7 +454,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
// Last button released: ACTION_UP
MotionEvent upEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_UP, pointerCount, pointerProperties,
pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0);
if (!Device.injectEvent(upEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) {
if (!Device.injectEvent(upEvent, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) {
return false;
}
}
@ -489,20 +465,27 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f,
DEFAULT_DEVICE_ID, 0, source, 0);
return Device.injectEvent(event, targetDisplayId, Device.INJECT_MODE_ASYNC);
return Device.injectEvent(event, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC);
}
private boolean injectScroll(Position position, float hScroll, float vScroll, int buttons) {
long now = SystemClock.uptimeMillis();
Pair<Point, Integer> pair = getEventPointAndDisplayId(position);
if (pair == null) {
// it hides the field on purpose, to read it with atomic access
@SuppressWarnings("checkstyle:HiddenField")
DisplayData displayData = this.displayData.get();
assert displayData != null : "Cannot receive a scroll event without a display";
Point point = displayData.positionMapper.map(position);
if (point == null) {
if (Ln.isEnabled(Ln.Level.VERBOSE)) {
Size eventSize = position.getScreenSize();
Size currentSize = displayData.positionMapper.getVideoSize();
Ln.v("Ignore scroll event generated for size " + eventSize + " (current size is " + currentSize + ")");
}
return false;
}
Point point = pair.first;
int targetDisplayId = pair.second;
MotionEvent.PointerProperties props = pointerProperties[0];
props.id = 0;
@ -514,7 +497,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f,
DEFAULT_DEVICE_ID, 0, InputDevice.SOURCE_MOUSE, 0);
return Device.injectEvent(event, targetDisplayId, Device.INJECT_MODE_ASYNC);
return Device.injectEvent(event, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC);
}
/**
@ -708,12 +691,9 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
}
private void setDisplayPower(boolean on) {
// Change the power of the main display when mirroring a virtual display
int targetDisplayId = displayId != Device.DISPLAY_ID_NONE ? displayId : 0;
boolean setDisplayPowerOk = Device.setDisplayPower(targetDisplayId, on);
boolean setDisplayPowerOk = Device.setDisplayPower(displayId, on);
if (setDisplayPowerOk) {
// Do not keep display power off for virtual displays: MOD+p must wake up the physical device
keepDisplayPowerOff = displayId != Device.DISPLAY_ID_NONE && !on;
keepDisplayPowerOff = !on;
Ln.i("Device display turned " + (on ? "on" : "off"));
if (cleanUp != null) {
boolean mustRestoreOnExit = !on;

View File

@ -40,10 +40,6 @@ public final class Device {
public static final int INJECT_MODE_WAIT_FOR_RESULT = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT;
public static final int INJECT_MODE_WAIT_FOR_FINISH = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH;
// The new display power method introduced in Android 15 does not work as expected:
// <https://github.com/Genymobile/scrcpy/issues/5530>
private static final boolean USE_ANDROID_15_DISPLAY_POWER = false;
private Device() {
// not instantiable
}
@ -131,7 +127,7 @@ public final class Device {
public static boolean setDisplayPower(int displayId, boolean on) {
assert displayId != Device.DISPLAY_ID_NONE;
if (USE_ANDROID_15_DISPLAY_POWER && Build.VERSION.SDK_INT >= AndroidVersions.API_35_ANDROID_15) {
if (Build.VERSION.SDK_INT >= AndroidVersions.API_35_ANDROID_15) {
return ServiceManager.getDisplayManager().requestDisplayPower(displayId, on);
}

View File

@ -3,14 +3,16 @@ package com.genymobile.scrcpy.device;
public final class NewDisplay {
private Size size;
private int dpi;
private float fps;
public NewDisplay() {
// Auto size and dpi
// Auto size, dpi and fps
}
public NewDisplay(Size size, int dpi) {
public NewDisplay(Size size, int dpi, float fps) {
this.size = size;
this.dpi = dpi;
this.fps = fps;
}
public Size getSize() {
@ -21,6 +23,10 @@ public final class NewDisplay {
return dpi;
}
public float getFps() {
return fps;
}
public boolean hasExplicitSize() {
return size != null;
}
@ -28,4 +34,8 @@ public final class NewDisplay {
public boolean hasExplicitDpi() {
return dpi != 0;
}
public boolean hasExplicitFps() {
return fps != 0;
}
}

View File

@ -14,8 +14,10 @@ import com.genymobile.scrcpy.util.AffineMatrix;
import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.annotation.SuppressLint;
import android.graphics.Rect;
import android.hardware.display.VirtualDisplay;
import android.hardware.display.VirtualDisplayConfig;
import android.os.Build;
import android.view.Surface;
@ -161,6 +163,7 @@ public class NewDisplayCapture extends SurfaceCapture {
displayTransform = AffineMatrix.multiplyAll(displayRotationMatrix, eventTransform);
}
@SuppressLint("WrongConstant")
public void startNew(Surface surface) {
int virtualDisplayId;
try {
@ -182,10 +185,30 @@ public class NewDisplayCapture extends SurfaceCapture {
| VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
}
}
virtualDisplay = ServiceManager.getDisplayManager()
.createNewVirtualDisplay("scrcpy", displaySize.getWidth(), displaySize.getHeight(), dpi, surface, flags);
// Since Android 14, it is possible to request a display frame rate:
// <https://android.googlesource.com/platform/frameworks/base/+/6c57176e9a2882eff03c5b3f3cccfd988d38488d>
// It defaults to 60 fps:
// <https://android.googlesource.com/platform/frameworks/base/+/6c57176e9a2882eff03c5b3f3cccfd988d38488d/services/core/java/com/android/server/display/VirtualDisplayAdapter.java#562>
float fps = newDisplay.getFps();
if (fps > 0) {
if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) {
VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
"scrcpy", displaySize.getWidth(), displaySize.getHeight(), dpi);
builder.setFlags(flags);
builder.setSurface(surface);
builder.setRequestedRefreshRate(fps);
virtualDisplay = ServiceManager.getDisplayManager().createNewVirtualDisplay(builder.build());
} else {
throw new UnsupportedOperationException("Setting the virtual display frame rate (@" + fps + ") requires Android >= 14");
}
} else {
virtualDisplay = ServiceManager.getDisplayManager()
.createNewVirtualDisplay("scrcpy", displaySize.getWidth(), displaySize.getHeight(), dpi, surface, flags);
}
virtualDisplayId = virtualDisplay.getDisplay().getDisplayId();
Ln.i("New display: " + displaySize.getWidth() + "x" + displaySize.getHeight() + "/" + dpi + " (id=" + virtualDisplayId + ")");
String fpsString = fps > 0 ? "@" + fps : "";
Ln.i("New display: " + displaySize.getWidth() + "x" + displaySize.getHeight() + "/" + dpi + fpsString + " (id=" + virtualDisplayId + ")");
displaySizeMonitor.start(virtualDisplayId, this::invalidate);
} catch (Exception e) {

View File

@ -112,8 +112,8 @@ public class SurfaceEncoder implements AsyncProcessor {
// The capture might have been closed internally (for example if the camera is disconnected)
alive = !stopped.get() && !capture.isClosed();
}
} catch (IllegalStateException | IllegalArgumentException | IOException e) {
Ln.e("Capture/encoding error: " + e.getClass().getName() + ": " + e.getMessage());
} catch (IllegalStateException | IllegalArgumentException e) {
Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage());
if (!prepareRetry(size)) {
throw e;
}

View File

@ -11,6 +11,7 @@ import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.hardware.display.VirtualDisplay;
import android.hardware.display.VirtualDisplayConfig;
import android.os.Handler;
import android.view.Display;
import android.view.Surface;
@ -46,6 +47,7 @@ public final class DisplayManager {
}
private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal
private android.hardware.display.DisplayManager displayManager;
private Method createVirtualDisplayMethod;
private Method requestDisplayPowerMethod;
@ -151,17 +153,31 @@ public final class DisplayManager {
return createVirtualDisplayMethod;
}
public VirtualDisplay createVirtualDisplay(String name, int width, int height, int displayIdToMirror, Surface surface) throws Exception {
public VirtualDisplay createVirtualDisplay(String name, int width, int height, int displayIdToMirror, Surface surface)
throws ReflectiveOperationException {
Method method = getCreateVirtualDisplayMethod();
return (VirtualDisplay) method.invoke(null, name, width, height, displayIdToMirror, surface);
}
public VirtualDisplay createNewVirtualDisplay(String name, int width, int height, int dpi, Surface surface, int flags) throws Exception {
Constructor<android.hardware.display.DisplayManager> ctor = android.hardware.display.DisplayManager.class.getDeclaredConstructor(
Context.class);
ctor.setAccessible(true);
android.hardware.display.DisplayManager dm = ctor.newInstance(FakeContext.get());
return dm.createVirtualDisplay(name, width, height, dpi, surface, flags);
private android.hardware.display.DisplayManager getAndroidDisplayManager() throws ReflectiveOperationException {
if (displayManager == null) {
Constructor<android.hardware.display.DisplayManager> ctor = android.hardware.display.DisplayManager.class.getDeclaredConstructor(
Context.class);
ctor.setAccessible(true);
displayManager = ctor.newInstance(FakeContext.get());
}
return displayManager;
}
public VirtualDisplay createNewVirtualDisplay(String name, int width, int height, int dpi, Surface surface, int flags)
throws ReflectiveOperationException {
return getAndroidDisplayManager().createVirtualDisplay(name, width, height, dpi, surface, flags);
}
@TargetApi(AndroidVersions.API_34_ANDROID_14)
public VirtualDisplay createNewVirtualDisplay(VirtualDisplayConfig config) throws ReflectiveOperationException {
return getAndroidDisplayManager().createVirtualDisplay(config);
}
private Method getRequestDisplayPowerMethod() throws NoSuchMethodException {
@ -192,9 +208,6 @@ public final class DisplayManager {
if ("onDisplayChanged".equals(method.getName())) {
listener.onDisplayChanged((int) args[0]);
}
if ("toString".equals(method.getName())) {
return "DisplayListener";
}
return null;
});
try {

View File

@ -0,0 +1,120 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.device.NewDisplay;
import com.genymobile.scrcpy.device.Size;
import org.junit.Assert;
import org.junit.Test;
public class OptionsTest {
@Test
public void testParseNewDisplayEmpty() {
NewDisplay newDisplay = Options.parseNewDisplay("");
Assert.assertFalse(newDisplay.hasExplicitSize());
Assert.assertFalse(newDisplay.hasExplicitDpi());
Assert.assertFalse(newDisplay.hasExplicitFps());
Assert.assertNull(newDisplay.getSize());
Assert.assertEquals(0, newDisplay.getDpi());
Assert.assertEquals(0, newDisplay.getFps(), 0);
}
@Test
public void testParseNewDisplaySizeOnly() {
NewDisplay newDisplay = Options.parseNewDisplay("1920x1080");
Assert.assertTrue(newDisplay.hasExplicitSize());
Assert.assertFalse(newDisplay.hasExplicitDpi());
Assert.assertFalse(newDisplay.hasExplicitFps());
Assert.assertEquals(new Size(1920, 1080), newDisplay.getSize());
Assert.assertEquals(0, newDisplay.getDpi());
Assert.assertEquals(0, newDisplay.getFps(), 0);
}
@Test
public void testParseNewDisplayDpiOnly() {
NewDisplay newDisplay = Options.parseNewDisplay("/240");
Assert.assertFalse(newDisplay.hasExplicitSize());
Assert.assertTrue(newDisplay.hasExplicitDpi());
Assert.assertFalse(newDisplay.hasExplicitFps());
Assert.assertNull(newDisplay.getSize());
Assert.assertEquals(240, newDisplay.getDpi());
Assert.assertEquals(0, newDisplay.getFps(), 0);
}
@Test
public void testParseNewDisplayFpsOnly() {
NewDisplay newDisplay = Options.parseNewDisplay("@30");
Assert.assertFalse(newDisplay.hasExplicitSize());
Assert.assertFalse(newDisplay.hasExplicitDpi());
Assert.assertTrue(newDisplay.hasExplicitFps());
Assert.assertNull(newDisplay.getSize());
Assert.assertEquals(0, newDisplay.getDpi());
Assert.assertEquals(30, newDisplay.getFps(), 0);
}
@Test
public void testParseNewDisplaySizeAndDpi() {
NewDisplay newDisplay = Options.parseNewDisplay("1920x1080/240");
Assert.assertTrue(newDisplay.hasExplicitSize());
Assert.assertTrue(newDisplay.hasExplicitDpi());
Assert.assertFalse(newDisplay.hasExplicitFps());
Assert.assertEquals(new Size(1920, 1080), newDisplay.getSize());
Assert.assertEquals(240, newDisplay.getDpi());
Assert.assertEquals(0, newDisplay.getFps(), 0);
}
@Test
public void testParseNewDisplaySizeAndFps() {
NewDisplay newDisplay = Options.parseNewDisplay("1920x1080@30");
Assert.assertTrue(newDisplay.hasExplicitSize());
Assert.assertFalse(newDisplay.hasExplicitDpi());
Assert.assertTrue(newDisplay.hasExplicitFps());
Assert.assertEquals(new Size(1920, 1080), newDisplay.getSize());
Assert.assertEquals(0, newDisplay.getDpi());
Assert.assertEquals(30, newDisplay.getFps(), 0);
}
@Test
public void testParseNewDisplaySizeAndDpiAndFps() {
NewDisplay newDisplay = Options.parseNewDisplay("1920x1080/240@30");
Assert.assertTrue(newDisplay.hasExplicitSize());
Assert.assertTrue(newDisplay.hasExplicitDpi());
Assert.assertTrue(newDisplay.hasExplicitFps());
Assert.assertEquals(new Size(1920, 1080), newDisplay.getSize());
Assert.assertEquals(240, newDisplay.getDpi());
Assert.assertEquals(30, newDisplay.getFps(), 0);
}
@Test
public void testParseNewDisplaySizeAndFpsAndDpi() {
NewDisplay newDisplay = Options.parseNewDisplay("1920x1080@30/240");
Assert.assertTrue(newDisplay.hasExplicitSize());
Assert.assertTrue(newDisplay.hasExplicitDpi());
Assert.assertTrue(newDisplay.hasExplicitFps());
Assert.assertEquals(new Size(1920, 1080), newDisplay.getSize());
Assert.assertEquals(240, newDisplay.getDpi());
Assert.assertEquals(30, newDisplay.getFps(), 0);
}
@Test
public void testParseNewDisplayDpiAndFps() {
NewDisplay newDisplay = Options.parseNewDisplay("/240@30");
Assert.assertFalse(newDisplay.hasExplicitSize());
Assert.assertTrue(newDisplay.hasExplicitDpi());
Assert.assertTrue(newDisplay.hasExplicitFps());
Assert.assertNull(newDisplay.getSize());
Assert.assertEquals(240, newDisplay.getDpi());
Assert.assertEquals(30, newDisplay.getFps(), 0);
}
@Test
public void testParseNewDisplayFpsAndDpi() {
NewDisplay newDisplay = Options.parseNewDisplay("@30/240");
Assert.assertFalse(newDisplay.hasExplicitSize());
Assert.assertTrue(newDisplay.hasExplicitDpi());
Assert.assertTrue(newDisplay.hasExplicitFps());
Assert.assertNull(newDisplay.getSize());
Assert.assertEquals(240, newDisplay.getDpi());
Assert.assertEquals(30, newDisplay.getFps(), 0);
}
}