Compare commits

..

15 Commits

Author SHA1 Message Date
4ef4838bf0 Add script to release a macOS static binary
Provide a prebuilt binary for macOS.

Co-authored-by: Muvaffak Onus <me@muvaf.com>
2024-11-23 17:08:25 +01:00
2a72a7fb40 Use generic command for SHA-256
The command sha256sum does not exist on macOS, but `shasum -a256` works
both on Linux and macOS.

Co-authored-by: Romain Vimont <rom@rom1v.com>
2024-11-23 17:07:51 +01:00
4771c89adf Add script to release a Linux static binary
Provide a prebuilt binary for Linux.
2024-11-23 17:07:49 +01:00
81f658a34d Add support for .tar.gz packaging
Make package_client.sh accept an archive format.
2024-11-22 22:23:37 +01:00
79e79cc765 Add static build option
Use static dependencies if the option is set.
2024-11-22 22:23:37 +01:00
d2e8f93a73 Add cross/native and shared/static for deps
Make dependencies build scripts more flexible, to accept a build type
(native or cross) and a link type (static or shared).

This will allow to build binaries for Linux and macOS.
2024-11-22 22:23:37 +01:00
e5f480f707 Make adb dependency script Windows-specific
This will allow adding similar scripts for other platforms.
2024-11-22 22:23:37 +01:00
1569de761b Extract args processing in deps scripts
Extract the code which process arguments to a function.

This will make it optional, so that the script which just
downloads the official adb binaries does not use arguments.
2024-11-22 22:23:37 +01:00
efea97a026 Store dependencies configure args in bash arrays
This will make it possible to easily add items conditionally.
2024-11-22 22:23:37 +01:00
39acd9a316 Disable VDPAU and VAAPI for FFmpeg build
They are not used, and this prevents Linux builds from working if the
dependency is not available.
2024-11-22 22:23:37 +01:00
2fe45b58ef Reorder FFmpeg configure args
All --disable, then all --enable.
2024-11-22 22:23:37 +01:00
0c840829b8 Preserve file permissions in GitHub Actions
The upload-artifact action does not preserve file permissions:
<https://github.com/actions/upload-artifact?#permission-loss>

Even if it is not critical for Windows releases, it will be for other
platforms. Wrap everything in a tarball to keep original permissions.
2024-11-22 22:23:37 +01:00
eb0246baae Split packaging for each target on CI
Create separate jobs for packaging the win32 and the win64 releases.
2024-11-22 22:23:37 +01:00
901d4ee3e1 Test scrcpy-server in a separate CI job
Use a separate GitHub Action job to build and test the server.
2024-11-22 22:23:37 +01:00
6af81e10ba Replace release.mk by release scripts
Since commit 2687d20280, the Makefile
named release.mk stopped handling dependencies between recipes, because
they have to be executed separately (from different Github Actions
jobs).

There is no real benefit using a Makefile anymore. Replace them by
several individual release scripts for simplicity and readability.
2024-11-22 22:23:37 +01:00
42 changed files with 314 additions and 681 deletions

View File

@ -42,30 +42,15 @@ jobs:
distribution: 'zulu' distribution: 'zulu'
java-version: '17' java-version: '17'
- name: Build - name: Build scrcpy-server
run: release/build_server.sh run: release/build_server.sh
- name: Upload artifact - name: Upload scrcpy-server artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: scrcpy-server name: scrcpy-server
path: release/work/build-server/server/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
test-client: test-client:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -74,52 +59,41 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt update
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \ sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \ libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \ libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev
libv4l-dev
- name: Test - name: Test
run: release/test_client.sh run: release/test_client.sh
build-linux-x86_64: build-linux:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check architecture
run: |
arch=$(uname -m)
if [[ "$arch" != x86_64 ]]
then
echo "Unexpected architecture: $arch" >&2
exit 1
fi
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \ sudo apt update
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \ sudo apt install -y meson ninja-build nasm libudev-dev
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
libv4l-dev
- name: Build - name: Build linux
run: release/build_linux.sh x86_64 run: release/build_linux.sh
# upload-artifact does not preserve permissions # upload-artifact does not preserve permissions
- name: Tar - name: Tar
run: | run: |
cd release/work/build-linux-x86_64 cd release/work/build-linux
mkdir dist-tar mkdir dist-tar
cd dist-tar cd dist-tar
tar -C .. -cvf dist.tar.gz dist/ tar -C .. -cvf dist.tar.gz dist/
- name: Upload artifact - name: Upload build-linux artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: build-linux-x86_64-intermediate name: build-linux-intermediate
path: release/work/build-linux-x86_64/dist-tar/ path: release/work/build-linux/dist-tar/
build-win32: build-win32:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -129,6 +103,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt update
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \ sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \ libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \ libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
@ -137,7 +112,7 @@ jobs:
- name: Workaround for old meson version run by Github Actions - name: Workaround for old meson version run by Github Actions
run: sed -i 's/^pkg-config/pkgconfig/' cross_win32.txt run: sed -i 's/^pkg-config/pkgconfig/' cross_win32.txt
- name: Build - name: Build win32
run: release/build_windows.sh 32 run: release/build_windows.sh 32
# upload-artifact does not preserve permissions # upload-artifact does not preserve permissions
@ -148,7 +123,7 @@ jobs:
cd dist-tar cd dist-tar
tar -C .. -cvf dist.tar.gz dist/ tar -C .. -cvf dist.tar.gz dist/
- name: Upload artifact - name: Upload build-win32 artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: build-win32-intermediate name: build-win32-intermediate
@ -162,6 +137,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt update
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \ sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \ libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \ libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
@ -170,7 +146,7 @@ jobs:
- name: Workaround for old meson version run by Github Actions - name: Workaround for old meson version run by Github Actions
run: sed -i 's/^pkg-config/pkgconfig/' cross_win64.txt run: sed -i 's/^pkg-config/pkgconfig/' cross_win64.txt
- name: Build - name: Build win64
run: release/build_windows.sh 64 run: release/build_windows.sh 64
# upload-artifact does not preserve permissions # upload-artifact does not preserve permissions
@ -181,24 +157,15 @@ jobs:
cd dist-tar cd dist-tar
tar -C .. -cvf dist.tar.gz dist/ tar -C .. -cvf dist.tar.gz dist/
- name: Upload artifact - name: Upload build-win64 artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: build-win64-intermediate name: build-win64-intermediate
path: release/work/build-win64/dist-tar/ path: release/work/build-win64/dist-tar/
build-macos-aarch64: build-macos:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- name: Check architecture
run: |
arch=$(uname -m)
if [[ "$arch" != arm64 ]]
then
echo "Unexpected architecture: $arch" >&2
exit 1
fi
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -207,63 +174,27 @@ jobs:
brew install meson ninja nasm libiconv zlib automake autoconf \ brew install meson ninja nasm libiconv zlib automake autoconf \
libtool libtool
- name: Build - name: Build macOS
run: release/build_macos.sh aarch64 run: release/build_macos.sh
# upload-artifact does not preserve permissions # upload-artifact does not preserve permissions
- name: Tar - name: Tar
run: | run: |
cd release/work/build-macos-aarch64 cd release/work/build-macos
mkdir dist-tar mkdir dist-tar
cd dist-tar cd dist-tar
tar -C .. -cvf dist.tar.gz dist/ tar -C .. -cvf dist.tar.gz dist/
- name: Upload artifact - name: Upload build-macos artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: build-macos-aarch64-intermediate name: build-macos-intermediate
path: release/work/build-macos-aarch64/dist-tar/ path: release/work/build-macos/dist-tar/
build-macos-x86_64: package-linux:
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: needs:
- build-scrcpy-server - build-scrcpy-server
- build-linux-x86_64 - build-linux
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@ -275,25 +206,25 @@ jobs:
name: scrcpy-server name: scrcpy-server
path: release/work/build-server/server/ path: release/work/build-server/server/
- name: Download build-linux-x86_64 - name: Download build-linux
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: build-linux-x86_64-intermediate name: build-linux-intermediate
path: release/work/build-linux-x86_64/dist-tar/ path: release/work/build-linux/dist-tar/
# upload-artifact does not preserve permissions # upload-artifact does not preserve permissions
- name: Detar - name: Detar
run: | run: |
cd release/work/build-linux-x86_64 cd release/work/build-linux
tar xf dist-tar/dist.tar.gz tar xf dist-tar/dist.tar.gz
- name: Package - name: Package linux
run: release/package_client.sh linux-x86_64 tar.gz run: release/package_client.sh linux tar.gz
- name: Upload release - name: Upload linux release
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: release-linux-x86_64 name: release-linux
path: release/output/ path: release/output/
package-win32: package-win32:
@ -323,10 +254,10 @@ jobs:
cd release/work/build-win32 cd release/work/build-win32
tar xf dist-tar/dist.tar.gz tar xf dist-tar/dist.tar.gz
- name: Package - name: Package win32
run: release/package_client.sh win32 zip run: release/package_client.sh win32 zip
- name: Upload release - name: Upload win32 release
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: release-win32 name: release-win32
@ -359,55 +290,19 @@ jobs:
cd release/work/build-win64 cd release/work/build-win64
tar xf dist-tar/dist.tar.gz tar xf dist-tar/dist.tar.gz
- name: Package - name: Package win64
run: release/package_client.sh win64 zip run: release/package_client.sh win64 zip
- name: Upload release - name: Upload win64 release
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: release-win64 name: release-win64
path: release/output path: release/output
package-macos-aarch64: package-macos:
needs: needs:
- build-scrcpy-server - build-scrcpy-server
- build-macos-aarch64 - build-macos
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 runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@ -422,32 +317,31 @@ jobs:
- name: Download build-macos - name: Download build-macos
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: build-macos-x86_64-intermediate name: build-macos-intermediate
path: release/work/build-macos-x86_64/dist-tar/ path: release/work/build-macos/dist-tar/
# upload-artifact does not preserve permissions # upload-artifact does not preserve permissions
- name: Detar - name: Detar
run: | run: |
cd release/work/build-macos-x86_64 cd release/work/build-macos
tar xf dist-tar/dist.tar.gz tar xf dist-tar/dist.tar.gz
- name: Package - name: Package macos
run: release/package_client.sh macos-x86_64 tar.gz run: release/package_client.sh macos tar.gz
- name: Upload release - name: Upload macos release
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: release-macos-x86_64 name: release-macos
path: release/output/ path: release/output/
release: release:
needs: needs:
- build-scrcpy-server - build-scrcpy-server
- package-linux-x86_64 - package-linux
- package-win32 - package-win32
- package-win64 - package-win64
- package-macos-aarch64 - package-macos
- package-macos-x86_64
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@ -459,10 +353,10 @@ jobs:
name: scrcpy-server name: scrcpy-server
path: release/work/build-server/server/ path: release/work/build-server/server/
- name: Download release-linux-x86_64 - name: Download release-linux
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: release-linux-x86_64 name: release-linux
path: release/output/ path: release/output/
- name: Download release-win32 - name: Download release-win32
@ -477,18 +371,6 @@ jobs:
name: release-win64 name: release-win64
path: release/output/ 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 - name: Package server
run: release/package_server.sh run: release/package_server.sh

View File

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

View File

@ -0,0 +1,6 @@
#!/bin/bash
cd "$(dirname ${BASH_SOURCE[0]})"
export ADB=./adb
export SCRCPY_SERVER_PATH=./scrcpy-server
export ICON=./icon.png
scrcpy "$@"

View File

@ -38,14 +38,6 @@ else
export CFLAGS='-static-libgcc -static' export CFLAGS='-static-libgcc -static'
export CXXFLAGS="$CFLAGS" export CXXFLAGS="$CFLAGS"
export LDFLAGS='-static-libgcc -static' 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"
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 fi
conf=( conf=(
@ -81,14 +73,8 @@ else
--enable-muxer=wav --enable-muxer=wav
) )
if [[ "$HOST" == linux ]] if [[ "$HOST" != linux ]]
then then
conf+=(
--enable-libv4l2
--enable-outdev=v4l2
--enable-encoder=rawvideo
)
else
# libavdevice is only used for V4L2 on Linux # libavdevice is only used for V4L2 on Linux
conf+=( conf+=(
--disable-avdevice --disable-avdevice

View File

@ -46,7 +46,6 @@ src = [
'src/util/acksync.c', 'src/util/acksync.c',
'src/util/audiobuf.c', 'src/util/audiobuf.c',
'src/util/average.c', 'src/util/average.c',
'src/util/env.c',
'src/util/file.c', 'src/util/file.c',
'src/util/intmap.c', 'src/util/intmap.c',
'src/util/intr.c', 'src/util/intr.c',
@ -170,6 +169,9 @@ conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
# run a server debugger and wait for a client to be attached # run a server debugger and wait for a client to be attached
conf.set('SERVER_DEBUGGER', get_option('server_debugger')) 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) # enable V4L2 support (linux only)
conf.set('HAVE_V4L2', v4l2_support) conf.set('HAVE_V4L2', v4l2_support)

View File

@ -13,7 +13,7 @@ BEGIN
VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "LegalCopyright", "Romain Vimont, Genymobile"
VALUE "OriginalFilename", "scrcpy.exe" VALUE "OriginalFilename", "scrcpy.exe"
VALUE "ProductName", "scrcpy" VALUE "ProductName", "scrcpy"
VALUE "ProductVersion", "3.0" VALUE "ProductVersion", "2.7"
END END
END END
BLOCK "VarFileInfo" 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"). 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 .TP
.B \-\-camera\-high\-speed .B \-\-camera\-high\-speed
Enable high-speed camera capture mode. 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. 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 .TP
.BI "\-\-camera\-size " width\fRx\fIheight .BI "\-\-camera\-size " width\fRx\fIheight
Specify an explicit camera capture size. Specify an explicit camera capture size.

View File

@ -7,7 +7,6 @@
#include "adb_device.h" #include "adb_device.h"
#include "adb_parser.h" #include "adb_parser.h"
#include "util/env.h"
#include "util/file.h" #include "util/file.h"
#include "util/log.h" #include "util/log.h"
#include "util/process_intr.h" #include "util/process_intr.h"
@ -25,45 +24,15 @@
*/ */
#define SC_ADB_COMMAND(...) { sc_adb_get_executable(), __VA_ARGS__, NULL } #define SC_ADB_COMMAND(...) { sc_adb_get_executable(), __VA_ARGS__, NULL }
static char *adb_executable; static const 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);
}
const char * const char *
sc_adb_get_executable(void) { sc_adb_get_executable(void) {
if (!adb_executable) {
adb_executable = getenv("ADB");
if (!adb_executable)
adb_executable = "adb";
}
return adb_executable; 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); 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) #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 * const char *
sc_adb_get_executable(void); sc_adb_get_executable(void);
@ -120,10 +114,4 @@ sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
char * char *
sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags); 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 #endif

View File

@ -255,6 +255,14 @@ static const struct sc_option options[] = {
"ratio), \"<num>:<den>\" (e.g. \"4:3\") or \"<value>\" (e.g. " "ratio), \"<num>:<den>\" (e.g. \"4:3\") or \"<value>\" (e.g. "
"\"1.6\")." "\"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_id = OPT_CAMERA_FACING,
.longopt = "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" .text = "Select the device camera by its facing direction.\n"
"Possible values are \"front\", \"back\" and \"external\".", "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_id = OPT_CAMERA_HIGH_SPEED,
.longopt = "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 " "This mode is restricted to specific resolutions and frame "
"rates, listed by --list-camera-sizes.", "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_id = OPT_CAMERA_SIZE,
.longopt = "camera-size", .longopt = "camera-size",
@ -292,21 +284,12 @@ static const struct sc_option options[] = {
.text = "Specify an explicit camera capture size.", .text = "Specify an explicit camera capture size.",
}, },
{ {
.longopt_id = OPT_CAPTURE_ORIENTATION, .longopt_id = OPT_CAMERA_FPS,
.longopt = "capture-orientation", .longopt = "camera-fps",
.argdesc = "value", .argdesc = "value",
.text = "Set the capture video orientation.\n" .text = "Specify the camera capture frame rate.\n"
"Possible values are 0, 90, 180, 270, flip0, flip90, flip180 " "If not specified, Android's default frame rate (30 fps) is "
"and flip270, possibly prefixed by '@'.\n" "used.",
"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.",
}, },
{ {
// Not really deprecated (--codec has never been released), but without // Not really deprecated (--codec has never been released), but without
@ -496,6 +479,23 @@ static const struct sc_option options[] = {
.longopt = "list-encoders", .longopt = "list-encoders",
.text = "List video and audio encoders available on the device.", .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 // deprecated
.longopt_id = OPT_LOCK_VIDEO_ORIENTATION, .longopt_id = OPT_LOCK_VIDEO_ORIENTATION,

View File

@ -9,7 +9,6 @@
#include "config.h" #include "config.h"
#include "compat.h" #include "compat.h"
#include "util/env.h"
#include "util/file.h" #include "util/file.h"
#include "util/log.h" #include "util/log.h"
#include "util/str.h" #include "util/str.h"
@ -20,22 +19,35 @@
static char * static char *
get_icon_path(void) { get_icon_path(void) {
char *icon_path = sc_get_env("SCRCPY_ICON_PATH"); #ifdef __WINDOWS__
if (icon_path) { 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 // 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); LOGD("Using SCRCPY_ICON_PATH: %s", icon_path);
return icon_path; return icon_path;
} }
#ifndef PORTABLE #ifndef PORTABLE
LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH); 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) { if (!icon_path) {
LOG_OOM(); LOG_OOM();
return NULL; return NULL;
} }
#else #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) { if (!icon_path) {
LOGE("Could not get icon path"); LOGE("Could not get icon path");
return NULL; return NULL;

View File

@ -9,7 +9,6 @@
#include "adb/adb.h" #include "adb/adb.h"
#include "util/binary.h" #include "util/binary.h"
#include "util/env.h"
#include "util/file.h" #include "util/file.h"
#include "util/log.h" #include "util/log.h"
#include "util/net_intr.h" #include "util/net_intr.h"
@ -26,22 +25,35 @@
static char * static char *
get_server_path(void) { get_server_path(void) {
char *server_path = sc_get_env("SCRCPY_SERVER_PATH"); #ifdef __WINDOWS__
if (server_path) { 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 // 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); LOGD("Using SCRCPY_SERVER_PATH: %s", server_path);
return server_path; return server_path;
} }
#ifndef PORTABLE #ifndef PORTABLE
LOGD("Using server: " SC_SERVER_PATH_DEFAULT); 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) { if (!server_path) {
LOG_OOM(); LOG_OOM();
return NULL; return NULL;
} }
#else #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) { if (!server_path) {
LOGE("Could not get local file path, " LOGE("Could not get local file path, "
"using " SC_SERVER_FILENAME " from current directory"); "using " SC_SERVER_FILENAME " from current directory");
@ -189,31 +201,18 @@ execute_server(struct sc_server *server,
cmd[count++] = "app_process"; cmd[count++] = "app_process";
#ifdef SERVER_DEBUGGER #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" # define SERVER_DEBUGGER_PORT "5005"
const char *dbg; cmd[count++] =
if (sdk_version < 28) { # ifdef SERVER_DEBUGGER_METHOD_NEW
// Android < 9 /* Android 9 and above */
dbg = "-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address=" "-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; 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;
#endif #endif
cmd[count++] = "/"; // unused cmd[count++] = "/"; // unused
cmd[count++] = "com.genymobile.scrcpy.Server"; cmd[count++] = "com.genymobile.scrcpy.Server";
cmd[count++] = SCRCPY_VERSION; cmd[count++] = SCRCPY_VERSION;
@ -401,14 +400,10 @@ execute_server(struct sc_server *server,
cmd[count++] = NULL; cmd[count++] = NULL;
#ifdef SERVER_DEBUGGER #ifdef SERVER_DEBUGGER
LOGI("Server debugger listening%s...", LOGI("Server debugger waiting for a client on device port "
sdk_version < 30 ? " on port " SERVER_DEBUGGER_PORT : ""); SERVER_DEBUGGER_PORT "...");
// For Android < 11, from the computer: // From the computer, run
// - run `adb forward tcp:5005 tcp:5005` // 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)
//
// Then, from Android Studio: Run > Debug > Edit configurations... // Then, from Android Studio: Run > Debug > Edit configurations...
// On the left, click on '+', "Remote", with: // On the left, click on '+', "Remote", with:
// Host: localhost // Host: localhost
@ -485,21 +480,14 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params,
// end of the program // end of the program
server->params = *params; server->params = *params;
bool ok = sc_adb_init(); bool ok = sc_mutex_init(&server->mutex);
if (!ok) { if (!ok) {
return false; return false;
} }
ok = sc_mutex_init(&server->mutex);
if (!ok) {
sc_adb_destroy();
return false;
}
ok = sc_cond_init(&server->cond_stopped); ok = sc_cond_init(&server->cond_stopped);
if (!ok) { if (!ok) {
sc_mutex_destroy(&server->mutex); sc_mutex_destroy(&server->mutex);
sc_adb_destroy();
return false; return false;
} }
@ -507,7 +495,6 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params,
if (!ok) { if (!ok) {
sc_cond_destroy(&server->cond_stopped); sc_cond_destroy(&server->cond_stopped);
sc_mutex_destroy(&server->mutex); sc_mutex_destroy(&server->mutex);
sc_adb_destroy();
return false; return false;
} }
@ -1149,6 +1136,4 @@ sc_server_destroy(struct sc_server *server) {
sc_intr_destroy(&server->intr); sc_intr_destroy(&server->intr);
sc_cond_destroy(&server->cond_stopped); sc_cond_destroy(&server->cond_stopped);
sc_mutex_destroy(&server->mutex); sc_mutex_destroy(&server->mutex);
sc_adb_destroy();
} }

View File

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

View File

@ -95,14 +95,9 @@ scrcpy_otg(struct scrcpy_options *options) {
// On Windows, only one process could open a USB device // On Windows, only one process could open a USB device
// <https://github.com/Genymobile/scrcpy/issues/2773> // <https://github.com/Genymobile/scrcpy/issues/2773>
LOGI("Killing adb server (if any)..."); LOGI("Killing adb server (if any)...");
if (sc_adb_init()) {
unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR; unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR;
// uninterruptible (intr == NULL), but in practice it's very quick // uninterruptible (intr == NULL), but in practice it's very quick
sc_adb_kill_server(NULL, flags); sc_adb_kill_server(NULL, flags);
sc_adb_destroy();
} else {
LOGW("Could not call adb executable, adb server not killed");
}
#endif #endif
static const struct sc_usb_callbacks cbs = { 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 #ifdef _WIN32
# include <ws2tcpip.h> # include <ws2tcpip.h>
typedef int socklen_t; typedef int socklen_t;
typedef SOCKET sc_raw_socket;
# define SC_RAW_SOCKET_NONE INVALID_SOCKET
#else #else
# include <sys/types.h> # include <sys/types.h>
# include <sys/socket.h> # include <sys/socket.h>
@ -21,6 +23,8 @@
typedef struct sockaddr_in SOCKADDR_IN; typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR; typedef struct sockaddr SOCKADDR;
typedef struct in_addr IN_ADDR; typedef struct in_addr IN_ADDR;
typedef int sc_raw_socket;
# define SC_RAW_SOCKET_NONE -1
#endif #endif
bool bool
@ -43,26 +47,17 @@ net_cleanup(void) {
#endif #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 static inline sc_socket
wrap(sc_raw_socket sock) { wrap(sc_raw_socket sock) {
#ifdef SC_SOCKET_CLOSE_ON_INTERRUPT #ifdef _WIN32
if (sock == SC_RAW_SOCKET_NONE) { if (sock == INVALID_SOCKET) {
return SC_SOCKET_NONE; return SC_SOCKET_NONE;
} }
struct sc_socket_wrapper *socket = malloc(sizeof(*socket)); struct sc_socket_windows *socket = malloc(sizeof(*socket));
if (!socket) { if (!socket) {
LOG_OOM(); LOG_OOM();
sc_raw_socket_close(sock); closesocket(sock);
return SC_SOCKET_NONE; return SC_SOCKET_NONE;
} }
@ -77,9 +72,9 @@ wrap(sc_raw_socket sock) {
static inline sc_raw_socket static inline sc_raw_socket
unwrap(sc_socket socket) { unwrap(sc_socket socket) {
#ifdef SC_SOCKET_CLOSE_ON_INTERRUPT #ifdef _WIN32
if (socket == SC_SOCKET_NONE) { if (socket == SC_SOCKET_NONE) {
return SC_RAW_SOCKET_NONE; return INVALID_SOCKET;
} }
return socket->socket; return socket->socket;
@ -88,6 +83,17 @@ unwrap(sc_socket socket) {
#endif #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 #ifndef HAVE_SOCK_CLOEXEC
// If SOCK_CLOEXEC does not exist, the flag must be set manually once the // If SOCK_CLOEXEC does not exist, the flag must be set manually once the
// socket is created // socket is created
@ -242,9 +248,9 @@ net_interrupt(sc_socket socket) {
sc_raw_socket raw_sock = unwrap(socket); sc_raw_socket raw_sock = unwrap(socket);
#ifdef SC_SOCKET_CLOSE_ON_INTERRUPT #ifdef _WIN32
if (!atomic_flag_test_and_set(&socket->closed)) { if (!atomic_flag_test_and_set(&socket->closed)) {
return sc_raw_socket_close(raw_sock); return !closesocket(raw_sock);
} }
return true; return true;
#else #else
@ -256,15 +262,15 @@ bool
net_close(sc_socket socket) { net_close(sc_socket socket) {
sc_raw_socket raw_sock = unwrap(socket); sc_raw_socket raw_sock = unwrap(socket);
#ifdef SC_SOCKET_CLOSE_ON_INTERRUPT #ifdef _WIN32
bool ret = true; bool ret = true;
if (!atomic_flag_test_and_set(&socket->closed)) { if (!atomic_flag_test_and_set(&socket->closed)) {
ret = sc_raw_socket_close(raw_sock); ret = !closesocket(raw_sock);
} }
free(socket); free(socket);
return ret; return ret;
#else #else
return sc_raw_socket_close(raw_sock); return !close(raw_sock);
#endif #endif
} }

View File

@ -7,36 +7,21 @@
#include <stdint.h> #include <stdint.h>
#ifdef _WIN32 #ifdef _WIN32
# include <winsock2.h> # 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> # include <stdatomic.h>
# define SC_SOCKET_NONE NULL # define SC_SOCKET_NONE NULL
typedef struct sc_socket_wrapper { typedef struct sc_socket_windows {
sc_raw_socket socket; SOCKET socket;
atomic_flag closed; atomic_flag closed;
} *sc_socket; } *sc_socket;
#else
#else // not _WIN32
# include <sys/socket.h>
# define SC_SOCKET_NONE -1 # define SC_SOCKET_NONE -1
typedef sc_raw_socket sc_socket; typedef int sc_socket;
#endif #endif
#define IPV4_LOCALHOST 0x7F000001 #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 sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
# client build dependencies # 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 # server build dependencies
sudo dnf install java-devel sudo dnf install java-devel
@ -233,10 +233,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server #### Option 2: Use prebuilt server
- [`scrcpy-server-v3.0`][direct-scrcpy-server] - [`scrcpy-server-v2.7`][direct-scrcpy-server]
<sub>SHA-256: `800044c62a94d5fc16f5ab9c86d45b1050eae3eb436514d1b0d2fe2646b894ea`</sub> <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 Download the prebuilt server somewhere, and specify its path during the Meson
configuration: configuration:

View File

@ -23,20 +23,14 @@ To control the device without mirroring:
scrcpy --no-video --no-audio scrcpy --no-video --no-audio
``` ```
By default, the mouse is disabled when video playback is turned off. By default, mouse mode is switched to UHID if video mirroring is disabled (a
relative mouse mode is required).
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
```
To also use a UHID keyboard, set it explicitly: To also use a UHID keyboard, set it explicitly:
```bash ```bash
scrcpy --no-video --no-audio --mouse=uhid --keyboard=uhid scrcpy --no-video --no-audio --keyboard=uhid
scrcpy --no-video --no-audio -MK # short version scrcpy --no-video --no-audio -K # short version
``` ```
To use AOA instead (over USB only): 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 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: Redirect that port to the computer:
```bash ```bash
adb forward tcp:5005 tcp:5005 adb forward tcp:5005 tcp:5005
``` ```
For Android >= 11, first find the listening port: In Android Studio, _Run_ > _Debug_ > _Edit configurations..._ On the left, click on
`+`, _Remote_, and fill the form:
```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:
- Host: `localhost` - Host: `localhost`
- Port: `5005` - Port: `5005`

View File

@ -2,23 +2,6 @@
## Install ## 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> <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: 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` - Snap: `snap install scrcpy`
- … (see [repology](https://repology.org/project/scrcpy/versions)) - … (see [repology](https://repology.org/project/scrcpy/versions))
### Latest version
### From an install script However, the packaged version is not always the latest release. To install the
latest release from `master`, follow this simplified process.
To install the latest release from `master`, follow this simplified process.
First, you need to install the required packages: First, you need to install the required packages:

View File

@ -2,23 +2,6 @@
## Install ## 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]: Scrcpy is available in [Homebrew]:
```bash ```bash
@ -30,7 +13,7 @@ brew install scrcpy
You need `adb`, accessible from your `PATH`. If you don't have it yet: You need `adb`, accessible from your `PATH`. If you don't have it yet:
```bash ```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: 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: try another one:
```bash ```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

@ -15,10 +15,8 @@ scrcpy --new-display=/240 # use the main display size and 240 dpi
On some devices, a launcher is available in the virtual display. On some devices, a launcher is available in the virtual display.
When no launcher is available (or if is explicitly disabled by When no launcher is available, the virtual display is empty. In that case, you
[`--no-vd-system-decorations`](#system-decorations)), the virtual display is must [start an Android app](device.md#start-android-app).
empty. In that case, you must [start an Android
app](device.md#start-android-app).
For example: For example:
@ -26,27 +24,12 @@ For example:
scrcpy --new-display=1920x1080 --start-app=org.videolan.vlc 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 ## System decorations
By default, virtual display system decorations are enabled. To disable them, use By default, virtual display system decorations are enabled. But some devices
`--no-vd-system-decorations`: might display a broken UI;
``` Use `--no-vd-system-decorations` to disable it.
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.
Note that if no app is started, no content will be rendered, so no video frame Note that if no app is started, no content will be rendered, so no video frame
will be produced at all. will be produced at all.

View File

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

View File

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

View File

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

View File

@ -4,5 +4,6 @@ option('prebuilt_server', type: 'string', description: 'Path of the prebuilt ser
option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable') 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('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', 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('v4l2', type: 'boolean', value: true, description: 'Enable V4L2 feature when supported')
option('usb', type: 'boolean', value: true, description: 'Enable HID/OTG features when supported') option('usb', type: 'boolean', value: true, description: 'Enable HID/OTG features when supported')

View File

@ -4,14 +4,7 @@ cd "$(dirname ${BASH_SOURCE[0]})"
. build_common . build_common
cd .. # root project dir cd .. # root project dir
if [[ $# != 1 ]] LINUX_BUILD_DIR="$WORK_DIR/build-linux"
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/adb_linux.sh
app/deps/sdl.sh linux native static app/deps/sdl.sh linux native static
@ -36,7 +29,7 @@ ninja -C "$LINUX_BUILD_DIR"
# Group intermediate outputs into a 'dist' directory # Group intermediate outputs into a 'dist' directory
mkdir -p "$LINUX_BUILD_DIR/dist" mkdir -p "$LINUX_BUILD_DIR/dist"
cp "$LINUX_BUILD_DIR"/app/scrcpy "$LINUX_BUILD_DIR/dist/" cp "$LINUX_BUILD_DIR"/app/scrcpy "$LINUX_BUILD_DIR/dist/scrcpy_bin"
cp app/data/icon.png "$LINUX_BUILD_DIR/dist/" cp app/data/icon.png "$LINUX_BUILD_DIR/dist/"
cp app/scrcpy.1 "$LINUX_BUILD_DIR/dist/" cp app/data/scrcpy_static_wrapper.sh "$LINUX_BUILD_DIR/dist/scrcpy"
cp -r "$ADB_INSTALL_DIR"/. "$LINUX_BUILD_DIR/dist/" cp -r "$ADB_INSTALL_DIR"/. "$LINUX_BUILD_DIR/dist/"

View File

@ -4,14 +4,7 @@ cd "$(dirname ${BASH_SOURCE[0]})"
. build_common . build_common
cd .. # root project dir cd .. # root project dir
if [[ $# != 1 ]] MACOS_BUILD_DIR="$WORK_DIR/build-macos"
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/adb_macos.sh
app/deps/sdl.sh macos native static app/deps/sdl.sh macos native static
@ -36,7 +29,7 @@ ninja -C "$MACOS_BUILD_DIR"
# Group intermediate outputs into a 'dist' directory # Group intermediate outputs into a 'dist' directory
mkdir -p "$MACOS_BUILD_DIR/dist" mkdir -p "$MACOS_BUILD_DIR/dist"
cp "$MACOS_BUILD_DIR"/app/scrcpy "$MACOS_BUILD_DIR/dist/" cp "$MACOS_BUILD_DIR"/app/scrcpy "$MACOS_BUILD_DIR/dist/scrcpy_bin"
cp app/data/icon.png "$MACOS_BUILD_DIR/dist/" cp app/data/icon.png "$MACOS_BUILD_DIR/dist/"
cp app/scrcpy.1 "$MACOS_BUILD_DIR/dist/" cp app/data/scrcpy_static_wrapper.sh "$MACOS_BUILD_DIR/dist/scrcpy"
cp -r "$ADB_INSTALL_DIR"/. "$MACOS_BUILD_DIR/dist/" cp -r "$ADB_INSTALL_DIR"/. "$MACOS_BUILD_DIR/dist/"

View File

@ -48,5 +48,4 @@ cp app/data/scrcpy-console.bat "$WINXX_BUILD_DIR/dist/"
cp app/data/scrcpy-noconsole.vbs "$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/icon.png "$WINXX_BUILD_DIR/dist/"
cp app/data/open_a_terminal_here.bat "$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/" cp -r "$ADB_INSTALL_DIR"/. "$WINXX_BUILD_DIR/dist/"

View File

@ -5,10 +5,9 @@ cd "$(dirname ${BASH_SOURCE[0]})"
cd "$OUTPUT_DIR" cd "$OUTPUT_DIR"
sha256sum "scrcpy-server-$VERSION" \ sha256sum "scrcpy-server-$VERSION" \
"scrcpy-linux-x86_64-$VERSION.tar.gz" \ "scrcpy-linux-$VERSION.tar.gz" \
"scrcpy-win32-$VERSION.zip" \ "scrcpy-win32-$VERSION.zip" \
"scrcpy-win64-$VERSION.zip" \ "scrcpy-win64-$VERSION.zip" \
"scrcpy-macos-aarch64-$VERSION.tar.gz" \ "scrcpy-macos-$VERSION.tar.gz" \
"scrcpy-macos-x86_64-$VERSION.tar.gz" \
| tee SHA256SUMS.txt | tee SHA256SUMS.txt
echo "Release checksums generated in $PWD/SHA256SUMS.txt" echo "Release checksums generated in $PWD/SHA256SUMS.txt"

View File

@ -14,39 +14,39 @@ fi
FORMAT=$2 FORMAT=$2
if [[ "$FORMAT" != zip && "$FORMAT" != tar.gz ]] if [[ "$2" != zip && "$2" != tar.gz ]]
then then
echo "Invalid format (expected zip or tar.gz): $FORMAT" >&2 echo "Invalid format (expected zip or tar.gz): $2" >&2
exit 1 exit 1
fi fi
BUILD_DIR="$WORK_DIR/build-$1" BUILD_DIR="$WORK_DIR/build-$1"
ARCHIVE_DIR="$BUILD_DIR/release-archive" ARCHIVE_DIR="$BUILD_DIR/release-archive"
TARGET_DIRNAME="scrcpy-$1-$VERSION" TARGET="scrcpy-$1-$VERSION"
rm -rf "$ARCHIVE_DIR/$TARGET_DIRNAME" rm -rf "$ARCHIVE_DIR/$TARGET"
mkdir -p "$ARCHIVE_DIR/$TARGET_DIRNAME" mkdir -p "$ARCHIVE_DIR/$TARGET"
cp -r "$BUILD_DIR/dist/." "$ARCHIVE_DIR/$TARGET_DIRNAME/" cp -r "$BUILD_DIR/dist/." "$ARCHIVE_DIR/$TARGET/"
cp "$WORK_DIR/build-server/server/scrcpy-server" "$ARCHIVE_DIR/$TARGET_DIRNAME/" cp "$WORK_DIR/build-server/server/scrcpy-server" "$ARCHIVE_DIR/$TARGET/"
mkdir -p "$OUTPUT_DIR" mkdir -p "$OUTPUT_DIR"
cd "$ARCHIVE_DIR" cd "$ARCHIVE_DIR"
rm -f "$OUTPUT_DIR/$TARGET_DIRNAME.$FORMAT" rm -f "$OUTPUT_DIR/$TARGET.$FORMAT"
case "$FORMAT" in case "$FORMAT" in
zip) zip)
zip -r "$OUTPUT_DIR/$TARGET_DIRNAME.zip" "$TARGET_DIRNAME" zip -r "$OUTPUT_DIR/$TARGET.zip" "$TARGET"
;; ;;
tar.gz) tar.gz)
tar cvzf "$OUTPUT_DIR/$TARGET_DIRNAME.tar.gz" "$TARGET_DIRNAME" tar cvf "$OUTPUT_DIR/$TARGET.tar.gz" "$TARGET"
;; ;;
*) *)
echo "Invalid format (expected zip or tar.gz): $FORMAT" >&2 echo "Invalid format (expected zip or tar.gz): $FORMAT" >&2
exit 1 exit 1
esac esac
rm -rf "$TARGET_DIRNAME" rm -rf "$TARGET"
cd - cd -
echo "Generated '$OUTPUT_DIR/$TARGET_DIRNAME.$FORMAT'" echo "Generated '$OUTPUT_DIR/$TARGET.$FORMAT'"

View File

@ -12,12 +12,12 @@ rm -rf output
./build_server.sh ./build_server.sh
./build_windows.sh 32 ./build_windows.sh 32
./build_windows.sh 64 ./build_windows.sh 64
./build_linux.sh x86_64 ./build_linux.sh
./package_server.sh ./package_server.sh
./package_client.sh win32 zip ./package_client.sh win32 zip
./package_client.sh win64 zip ./package_client.sh win64 zip
./package_client.sh linux-x86_64 tar.gz ./package_client.sh linux tar.gz
./generate_checksums.sh ./generate_checksums.sh

View File

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

View File

@ -12,7 +12,7 @@
set -e set -e
SCRCPY_DEBUG=false SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=3.0 SCRCPY_VERSION_NAME=2.7
PLATFORM=${ANDROID_PLATFORM:-35} PLATFORM=${ANDROID_PLATFORM:-35}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0}

View File

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

View File

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

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) // The capture might have been closed internally (for example if the camera is disconnected)
alive = !stopped.get() && !capture.isClosed(); alive = !stopped.get() && !capture.isClosed();
} }
} catch (IllegalStateException | IllegalArgumentException | IOException e) { } catch (IllegalStateException | IllegalArgumentException e) {
Ln.e("Capture/encoding error: " + e.getClass().getName() + ": " + e.getMessage()); Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage());
if (!prepareRetry(size)) { if (!prepareRetry(size)) {
throw e; throw e;
} }

View File

@ -192,9 +192,6 @@ public final class DisplayManager {
if ("onDisplayChanged".equals(method.getName())) { if ("onDisplayChanged".equals(method.getName())) {
listener.onDisplayChanged((int) args[0]); listener.onDisplayChanged((int) args[0]);
} }
if ("toString".equals(method.getName())) {
return "DisplayListener";
}
return null; return null;
}); });
try { try {