Compare commits

...

52 Commits

Author SHA1 Message Date
aea6a371aa Remove scrcpy wrapper script for static builds
All portable builds now use the files located in the same directory as
the scrcpy executable by default.

PR #5560 <https://github.com/Genymobile/scrcpy/pull/5560>
2024-12-02 18:23:18 +01:00
dc6c279b1e Log adb executable path
Log the ADB executable path (at the DEBUG level) if it is not the
default one.

PR #5560 <https://github.com/Genymobile/scrcpy/pull/5560>
2024-12-02 18:23:18 +01:00
6d0ac3626d Use local adb in portable builds
For non-Windows portable builds, use the absolute path to the adb
executable located in the same directory as scrcpy.

On Windows, just use "adb", which is sufficient to use the local one.

PR #5560 <https://github.com/Genymobile/scrcpy/pull/5560>
2024-12-02 18:23:18 +01:00
beee42fb06 Load ADB value using sc_get_env()
Contrary to getenv(), the result of sc_get_env() is encoded in UTF-8 on
all platforms. Since it is allocated, it requires an explicit init() and
destroy() functions.

PR #5560 <https://github.com/Genymobile/scrcpy/pull/5560>
2024-12-02 18:23:18 +01:00
131372d2c4 Expose sc_get_env() to read environment variable
Contrary to getenv(), sc_get_env() returns an allocated string that is
guaranteed to be encoded in UTF-8 on all platforms (it uses _wgetenv()
internally on Windows and converts the strings).

PR #5560 <https://github.com/Genymobile/scrcpy/pull/5560>
2024-12-02 18:23:18 +01:00
0fd7534bd5 Add method to get executable path on MacOS
PR #5560 <https://github.com/Genymobile/scrcpy/pull/5560>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-12-02 18:23:18 +01:00
36574d2ee7 Fix .tar.gz compression
The generated .tar.gz releases were in fact non-gzipped tarballs.

Fixes #5581 <https://github.com/Genymobile/scrcpy/issues/5581>
2024-12-02 08:54:57 +01:00
3b2b3625e4 Accept positional control events without display
The position of touch and scroll must normally be "resolved" with a
"position mapper" associated to the display.

But to support the injection of such events with scrcpy-server alone
without video, handle the case where there is no display.

Fixes #5542 <https://github.com/Genymobile/scrcpy/issues/5542>
2024-12-01 17:22:47 +01:00
b2cdaa4bdc Factorize position mapper resolution
The code was duplicated for touch and scroll events. Extract it to a
private function.

Refs #5542 <https://github.com/Genymobile/scrcpy/issues/5542>
2024-12-01 17:22:47 +01:00
d01373c03c Enable close-on-interrupt for macOS
This behavior is also necessary on macOS.

Fixes #5536 <https://github.com/Genymobile/scrcpy/issues/5536>
2024-11-28 21:02:51 +01:00
ff06b6dcc1 Split network macro conditions
On Windows, interrupting a socket with shutdown() does not wake up
accept() or read() calls, the socket must be closed.

Introduce a new macro constant SC_SOCKET_CLOSE_ON_INTERRUPT, distinct of
_WIN32, because Windows will not be the only platform exhibiting this
behavior.

Refs #5536 <https://github.com/Genymobile/scrcpy/issues/5536>
2024-11-28 21:02:31 +01:00
017a3672a4 Check GitHub runner architecture
Make sure that the releases are built for the expected target arch.
2024-11-28 20:09:21 +01:00
c1351b250e Build macOS x86_64 release
Add actions to build a release for macOS x86_64 in addition to the
aarch64 version.

PR #5526 <https://github.com/Genymobile/scrcpy/pull/5526>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-11-28 20:09:21 +01:00
618a978f5b Specify architecture for Linux and macOS releases
PR #5526 <https://github.com/Genymobile/scrcpy/pull/5526>

Co-authored-by: Genxster1998 <ck.2229.ck@gmail.com>
2024-11-28 20:09:18 +01:00
acddd811bf Rename TARGET to TARGET_DIRNAME
This avoids confusion with "$1", which is also documented as "<target>".

If "$1" (the target) is "linux", then TARGET_DIRNAME is
"scrcpy-linux-v3.0".
2024-11-28 19:59:06 +01:00
ee9f7126ff Use FORMAT variable name in package_client.sh
The format is used several times, avoid using "$2" directly.
2024-11-28 19:58:41 +01:00
a18ed1ee7a Simplify GitHub actions step descriptions
Each step is executed within the context of an action, so mentioning the
name of the action is unnecessary.
2024-11-28 19:58:08 +01:00
678025b316 Remove apt update on GitHub Actions
Assume the image is up-to-date.
2024-11-28 19:40:47 +01:00
3e689020ba Fix null return value in DisplayManager.toString()
Ensure DisplayListener.toString() returns a non-null value to prevent a
NullPointerException on certain devices.

Fixes #5537 <https://github.com/Genymobile/scrcpy/issues/5537>
2024-11-27 07:45:35 +01:00
3d1f036c04 Rollback to old --turn-screen-off for Android 15
When the screen is turned off with the new display power method
introduced in Android 15, video mirroring freezes.

Use the Android 14 method for Android 15.

Refs 58ba00fa06
Refs #5418 <https://github.com/Genymobile/scrcpy/pull/5418>
Fixes #5530 <https://github.com/Genymobile/scrcpy/issues/5530>
2024-11-26 15:55:16 +01:00
3d5294c1e5 Set main display power for virtual display
Change the display power of the main display when mirroring a virtual
display, to make it possible to turn off the screen.

Fixes #5522 <https://github.com/Genymobile/scrcpy/issues/5522>
Refs #5530 <https://github.com/Genymobile/scrcpy/issues/5530>
2024-11-26 15:43:41 +01:00
1d2f16dbb5 Fix documentation about default mouse mode
When video playback is turned off, the default mouse mode has changed
from "uhid" to "disabled" in 2c25fd7a80.

Update the documentation accordingly.

Refs #5410 <https://github.com/Genymobile/scrcpy/issues/5410>
Refs #5542 <https://github.com/Genymobile/scrcpy/issues/5542>
2024-11-26 14:10:11 +01:00
7fef051976 Add BlueSky link
Scrcpy now has a BlueSky account.
2024-11-25 20:06:32 +01:00
da8ade88fd Fix link to virtual display doc in README
PR #5525 <https://github.com/Genymobile/scrcpy/pull/5525>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-11-25 08:21:06 +01:00
74aecc00b5 Update links to 3.0 2024-11-24 18:30:01 +01:00
5e05f2a25b Bump version to 3.0 2024-11-24 17:52:54 +01:00
3d478d7d5b Build FFmpeg with v4l2 support for Linux
So that --v4l2-sink works with Linux static builds.
2024-11-24 17:52:53 +01:00
54e1f8e060 Include scrcpy manpage in Linux and macOS releases 2024-11-24 16:50:47 +01:00
d40224f299 Fix alphabetic order of cli args 2024-11-24 16:37:32 +01:00
0628ffcb0b Merge branch 'master' into release 2024-11-24 16:01:05 +01:00
6f9520f3e2 Test build_without_gradle.sh in GitHub Actions
Build the server without gradle to make sure that the script works.

PR #5515 <https://github.com/Genymobile/scrcpy/pull/5515>
2024-11-24 15:46:37 +01:00
a7efb180b9 Add script to release macOS static binary
Provide a prebuilt binary for macOS.

Fixes #1733 <https://github.com/Genymobile/scrcpy/issues/1733>
Fixes #3235 <https://github.com/Genymobile/scrcpy/issues/3235>
Fixes #4489 <https://github.com/Genymobile/scrcpy/issues/4489>
Fixes #5327 <https://github.com/Genymobile/scrcpy/issues/5327>
PR #5515 <https://github.com/Genymobile/scrcpy/pull/5515>

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

PR #5515 <https://github.com/Genymobile/scrcpy/pull/5515>

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-11-24 15:41:13 +01:00
cb19686d79 Add script to release Linux static binary
Provide a prebuilt binary for Linux.

Fixes #5327 <https://github.com/Genymobile/scrcpy/issues/5327>
PR #5515 <https://github.com/Genymobile/scrcpy/pull/5515>
2024-11-24 15:41:13 +01:00
93da693e8c Add support for .tar.gz packaging
Make package_client.sh accept an archive format.

PR #5515 <https://github.com/Genymobile/scrcpy/pull/5515>
2024-11-24 15:41:13 +01:00
179c664e2b Add static build option
Use static dependencies if the option is set.

PR #5515 <https://github.com/Genymobile/scrcpy/pull/5515>
2024-11-24 15:41:13 +01:00
360936248c Add support for build and link types for deps
Make dependencies build scripts more flexible, to accept a build type
(native or cross) and a link type (static or shared).

This lays the groundwork for building binaries for Linux and macOS.

PR #5515 <https://github.com/Genymobile/scrcpy/pull/5515>
2024-11-24 15:41:13 +01:00
98d2065d6d Make the ADB dependency script Windows-specific
This will allow adding similar scripts for other platforms.

PR #5515 <https://github.com/Genymobile/scrcpy/pull/5515>
2024-11-24 15:41:13 +01:00
6a81fc438b Extract args processing in deps scripts
Extract the code that processes arguments into a function.

This will make it optional, so the script that only downloads the
official ADB binaries will not use arguments.

PR #5515 <https://github.com/Genymobile/scrcpy/pull/5515>
2024-11-24 15:41:13 +01:00
cf0098abf0 Store dependencies configure args in bash arrays
This will make it easy to conditionally add items.

PR #5515 <https://github.com/Genymobile/scrcpy/pull/5515>
2024-11-24 15:41:13 +01:00
73b595c806 Disable VDPAU and VAAPI for FFmpeg build
They are not used, and this prevents Linux builds from working if the
dependencies are unavailable.

PR #5515 <https://github.com/Genymobile/scrcpy/pull/5515>
2024-11-24 15:41:13 +01:00
d74f564f56 Reorder FFmpeg configure args
All --disable, then all --enable.

PR #5515 <https://github.com/Genymobile/scrcpy/pull/5515>
2024-11-24 15:41:13 +01:00
7fc6943284 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.

PR #5515 <https://github.com/Genymobile/scrcpy/pull/5515>
2024-11-24 15:41:13 +01:00
a57180047c Split packaging for each target on CI
Create separate jobs for packaging win32 and win64 releases.

PR #5515 <https://github.com/Genymobile/scrcpy/pull/5515>
2024-11-24 15:41:13 +01:00
5df218d8f9 Test scrcpy-server in a separate CI job
Use a separate GitHub Action job to build and test the server.

PR #5515 <https://github.com/Genymobile/scrcpy/pull/5515>
2024-11-24 15:41:13 +01:00
26bf209617 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).

Using a Makefile no longer provides any real benefit. Replace it by
several individual release scripts for simplicity and readability.

Refs #5306 <https://github.com/Genymobile/scrcpy/pull/5306>
PR #5515 <https://github.com/Genymobile/scrcpy/pull/5515>
2024-11-24 15:40:34 +01:00
dc82425769 Add debugging method for Android >= 11
Fixes #5346 <https://github.com/Genymobile/scrcpy/issues/5346>
PR #5466 <https://github.com/Genymobile/scrcpy/pull/5466>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-11-24 12:24:32 +01:00
9f39a5f2d6 Determine debugger command at runtime
When server_debugger is enabled, retrieve the device SDK version to
execute the correct command.

PR #5466 <https://github.com/Genymobile/scrcpy/pull/5466>
2024-11-22 11:04:32 +01:00
f1f2711626 Document missing --cask option for macOS
Installing android-platform-tools via brew install requires the option
--cask.

Refs #2004 <https://github.com/Genymobile/scrcpy/pull/2004>
Refs #2231 <https://github.com/Genymobile/scrcpy/pull/2231>
PR #5398 <https://github.com/Genymobile/scrcpy/pull/5398>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-11-20 08:04:47 +01:00
04dd72b594 Add "how to run" link for Windows
Reference the documentation explaining how to run scrcpy on Windows
directly in the main README.
2024-11-13 12:56:35 +01:00
762816cac6 Remove quotes for --video-encoder in documentation
Refs ec602a0334
2024-11-13 12:54:25 +01:00
67d4dfb5ff Add missing client build dependency in Fedora
PR #5147 <https://github.com/Genymobile/scrcpy/pull/5147>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-10-23 20:05:52 +02:00
53 changed files with 1395 additions and 507 deletions

View File

@ -6,11 +6,15 @@ on:
name: name:
description: 'Version name (default is ref name)' description: 'Version name (default is ref name)'
env:
# $VERSION is used by release scripts
VERSION: ${{ github.event.inputs.name || github.ref_name }}
jobs: jobs:
build-scrcpy-server: test-scrcpy-server:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
GRADLE: gradle # use native gradle instead of ./gradlew in release.mk GRADLE: gradle # use native gradle instead of ./gradlew in scripts
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -22,16 +26,45 @@ jobs:
java-version: '17' java-version: '17'
- name: Test scrcpy-server - name: Test scrcpy-server
run: make -f release.mk test-server run: release/test_server.sh
- name: Build scrcpy-server build-scrcpy-server:
run: make -f release.mk build-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: Upload scrcpy-server artifact - name: Setup JDK
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
- name: Build
run: release/build_server.sh
- name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: scrcpy-server name: scrcpy-server
path: 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
@ -41,18 +74,52 @@ 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: Build
run: |
meson setup d -Db_sanitize=address,undefined
- name: Test - name: Test
run: release/test_client.sh
build-linux-x86_64:
runs-on: ubuntu-latest
steps:
- name: Check architecture
run: | run: |
meson test -Cd 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
- 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/
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: build-linux-x86_64-intermediate
path: release/work/build-linux-x86_64/dist-tar/
build-win32: build-win32:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -62,7 +129,6 @@ 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 \
@ -71,14 +137,22 @@ 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 scrcpy win32 - name: Build
run: make -f release.mk build-win32 run: release/build_windows.sh 32
- name: Upload build-win32 artifact # 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
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: build-win32-intermediate name: build-win32-intermediate
path: build-win32/dist/ path: release/work/build-win32/dist-tar/
build-win64: build-win64:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -88,7 +162,6 @@ 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 \
@ -97,24 +170,101 @@ 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 scrcpy win64 - name: Build
run: make -f release.mk build-win64 run: release/build_windows.sh 64
- name: Upload build-win64 artifact # 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
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: build-win64-intermediate name: build-win64-intermediate
path: build-win64/dist/ path: release/work/build-win64/dist-tar/
package: 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: needs:
- build-scrcpy-server - build-scrcpy-server
- build-win32 - build-linux-x86_64
- build-win64
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
# $VERSION is used by release.mk
VERSION: ${{ github.event.inputs.name || github.ref_name }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -123,25 +273,230 @@ jobs:
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: scrcpy-server name: scrcpy-server
path: build-server/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:
needs:
- build-scrcpy-server
- build-win32
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-win32 - name: Download build-win32
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: build-win32-intermediate name: build-win32-intermediate
path: build-win32/dist/ 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/
- name: Download build-win64 - name: Download build-win64
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: build-win64-intermediate name: build-win64-intermediate
path: build-win64/dist/ 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
- name: Package - name: Package
run: make -f release.mk 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
- name: Upload release artifact - name: Upload release artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: scrcpy-release-${{ env.VERSION }} name: scrcpy-release-${{ env.VERSION }}
path: release-${{ env.VERSION }} path: release/output

View File

@ -2,7 +2,7 @@
source for the project. Do not download releases from random websites, even if source for the project. Do not download releases from random websites, even if
their name contains `scrcpy`.** their name contains `scrcpy`.**
# scrcpy (v2.7) # scrcpy (v3.0)
<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) - [Windows](doc/windows.md) (read [how to run](doc/windows.md#run))
- [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_displays.md) - [Virtual display](doc/virtual_display.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,6 +181,7 @@ 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)

29
app/deps/adb_linux.sh Executable file
View File

@ -0,0 +1,29 @@
#!/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/"

29
app/deps/adb_macos.sh Executable file
View File

@ -0,0 +1,29 @@
#!/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

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

View File

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

View File

@ -3,6 +3,7 @@ set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR" cd "$DEPS_DIR"
. common . common
process_args "$@"
VERSION=7.1 VERSION=7.1
FILENAME=ffmpeg-$VERSION.tar.xz FILENAME=ffmpeg-$VERSION.tar.xz
@ -22,68 +23,121 @@ fi
mkdir -p "$BUILD_DIR/$PROJECT_DIR" mkdir -p "$BUILD_DIR/$PROJECT_DIR"
cd "$BUILD_DIR/$PROJECT_DIR" cd "$BUILD_DIR/$PROJECT_DIR"
if [[ "$HOST" = win32 ]] if [[ -d "$DIRNAME" ]]
then then
ARCH=x86 echo "'$PWD/$DIRNAME' already exists, not reconfigured"
elif [[ "$HOST" = win64 ]] cd "$DIRNAME"
then
ARCH=x86_64
else else
mkdir "$DIRNAME"
cd "$DIRNAME"
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"
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
conf=(
--prefix="$INSTALL_DIR/$DIRNAME"
--extra-cflags="-O2 -fPIC"
--disable-programs
--disable-doc
--disable-swscale
--disable-postproc
--disable-avfilter
--disable-network
--disable-everything
--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 echo "Unsupported host: $HOST" >&2
exit 1 exit 1
fi esac
fi
# -static-libgcc to avoid missing libgcc_s_dw2-1.dll "$SOURCES_DIR/$PROJECT_DIR"/configure "${conf[@]}"
# -static to avoid dynamic dependency to zlib
export CFLAGS='-static-libgcc -static'
export CXXFLAGS="$CFLAGS"
export LDFLAGS='-static-libgcc -static'
if [[ -d "$HOST" ]]
then
echo "'$PWD/$HOST' already exists, not reconfigured"
cd "$HOST"
else
mkdir "$HOST"
cd "$HOST"
"$SOURCES_DIR/$PROJECT_DIR"/configure \
--prefix="$INSTALL_DIR/$HOST" \
--enable-cross-compile \
--target-os=mingw32 \
--arch="$ARCH" \
--cross-prefix="${HOST_TRIPLET}-" \
--cc="${HOST_TRIPLET}-gcc" \
--extra-cflags="-O2 -fPIC" \
--enable-shared \
--disable-static \
--disable-programs \
--disable-doc \
--disable-swscale \
--disable-postproc \
--disable-avfilter \
--disable-avdevice \
--disable-network \
--disable-everything \
--enable-swresample \
--enable-decoder=h264 \
--enable-decoder=hevc \
--enable-decoder=av1 \
--enable-decoder=pcm_s16le \
--enable-decoder=opus \
--enable-decoder=aac \
--enable-decoder=flac \
--enable-decoder=png \
--enable-protocol=file \
--enable-demuxer=image2 \
--enable-parser=png \
--enable-zlib \
--enable-muxer=matroska \
--enable-muxer=mp4 \
--enable-muxer=opus \
--enable-muxer=flac \
--enable-muxer=wav \
--disable-vulkan
fi fi
make -j make -j

View File

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

View File

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

View File

@ -46,6 +46,7 @@ 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',
@ -109,20 +110,22 @@ endif
cc = meson.get_compiler('c') cc = meson.get_compiler('c')
static = get_option('static')
dependencies = [ dependencies = [
dependency('libavformat', version: '>= 57.33'), dependency('libavformat', version: '>= 57.33', static: static),
dependency('libavcodec', version: '>= 57.37'), dependency('libavcodec', version: '>= 57.37', static: static),
dependency('libavutil'), dependency('libavutil', static: static),
dependency('libswresample'), dependency('libswresample', static: static),
dependency('sdl2', version: '>= 2.0.5'), dependency('sdl2', version: '>= 2.0.5', static: static),
] ]
if v4l2_support if v4l2_support
dependencies += dependency('libavdevice') dependencies += dependency('libavdevice', static: static)
endif endif
if usb_support if usb_support
dependencies += dependency('libusb-1.0') dependencies += dependency('libusb-1.0', static: static)
endif endif
if host_machine.system() == 'windows' if host_machine.system() == 'windows'
@ -167,9 +170,6 @@ 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", "2.7" VALUE "ProductVersion", "3.0"
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
.B \-\-camera\-high\-speed
Enable high-speed camera capture mode.
This mode is restricted to specific resolutions and frame rates, listed by \fB\-\-list\-camera\-sizes\fR.
.TP
.BI "\-\-camera\-id " id
Specify the device camera id to mirror.
The available camera ids can be listed by \fB\-\-list\-cameras\fR.
.TP .TP
.BI "\-\-camera\-facing " facing .BI "\-\-camera\-facing " facing
Select the device camera by its facing direction. Select the device camera by its facing direction.
@ -121,6 +109,18 @@ Specify the camera capture frame rate.
If not specified, Android's default frame rate (30 fps) is used. If not specified, Android's default frame rate (30 fps) is used.
.TP
.B \-\-camera\-high\-speed
Enable high-speed camera capture mode.
This mode is restricted to specific resolutions and frame rates, listed by \fB\-\-list\-camera\-sizes\fR.
.TP
.BI "\-\-camera\-id " id
Specify the device camera id to mirror.
The available camera ids can be listed by \fB\-\-list\-cameras\fR.
.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,6 +7,7 @@
#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"
@ -24,15 +25,45 @@
*/ */
#define SC_ADB_COMMAND(...) { sc_adb_get_executable(), __VA_ARGS__, NULL } #define SC_ADB_COMMAND(...) { sc_adb_get_executable(), __VA_ARGS__, NULL }
static const char *adb_executable; 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);
}
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;
} }
@ -739,3 +770,21 @@ 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,6 +15,12 @@
#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);
@ -114,4 +120,10 @@ 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,14 +255,6 @@ 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",
@ -270,6 +262,14 @@ 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,6 +277,14 @@ 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",
@ -284,12 +292,21 @@ static const struct sc_option options[] = {
.text = "Specify an explicit camera capture size.", .text = "Specify an explicit camera capture size.",
}, },
{ {
.longopt_id = OPT_CAMERA_FPS, .longopt_id = OPT_CAPTURE_ORIENTATION,
.longopt = "camera-fps", .longopt = "capture-orientation",
.argdesc = "value", .argdesc = "value",
.text = "Specify the camera capture frame rate.\n" .text = "Set the capture video orientation.\n"
"If not specified, Android's default frame rate (30 fps) is " "Possible values are 0, 90, 180, 270, flip0, flip90, flip180 "
"used.", "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.",
}, },
{ {
// Not really deprecated (--codec has never been released), but without // Not really deprecated (--codec has never been released), but without
@ -479,23 +496,6 @@ 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,6 +9,7 @@
#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"
@ -19,35 +20,22 @@
static char * static char *
get_icon_path(void) { get_icon_path(void) {
#ifdef __WINDOWS__ char *icon_path = sc_get_env("SCRCPY_ICON_PATH");
const wchar_t *icon_path_env = _wgetenv(L"SCRCPY_ICON_PATH"); if (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);
char *icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH); icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH);
if (!icon_path) { if (!icon_path) {
LOG_OOM(); LOG_OOM();
return NULL; return NULL;
} }
#else #else
char *icon_path = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME); 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,6 +9,7 @@
#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"
@ -25,35 +26,22 @@
static char * static char *
get_server_path(void) { get_server_path(void) {
#ifdef __WINDOWS__ char *server_path = sc_get_env("SCRCPY_SERVER_PATH");
const wchar_t *server_path_env = _wgetenv(L"SCRCPY_SERVER_PATH"); if (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);
char *server_path = strdup(SC_SERVER_PATH_DEFAULT); server_path = strdup(SC_SERVER_PATH_DEFAULT);
if (!server_path) { if (!server_path) {
LOG_OOM(); LOG_OOM();
return NULL; return NULL;
} }
#else #else
char *server_path = sc_file_get_local_path(SC_SERVER_FILENAME); 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");
@ -201,18 +189,31 @@ 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"
cmd[count++] = const char *dbg;
# ifdef SERVER_DEBUGGER_METHOD_NEW if (sdk_version < 28) {
/* Android 9 and above */ // Android < 9
"-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y," dbg = "-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
"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;
@ -400,10 +401,14 @@ execute_server(struct sc_server *server,
cmd[count++] = NULL; cmd[count++] = NULL;
#ifdef SERVER_DEBUGGER #ifdef SERVER_DEBUGGER
LOGI("Server debugger waiting for a client on device port " LOGI("Server debugger listening%s...",
SERVER_DEBUGGER_PORT "..."); sdk_version < 30 ? " on port " SERVER_DEBUGGER_PORT : "");
// From the computer, run // For Android < 11, from the computer:
// adb forward tcp:5005 tcp:5005 // - 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)
//
// 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
@ -480,14 +485,21 @@ 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_mutex_init(&server->mutex); bool ok = sc_adb_init();
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;
} }
@ -495,6 +507,7 @@ 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;
} }
@ -1136,4 +1149,6 @@ 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,6 +6,9 @@
#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"
@ -60,11 +63,22 @@ sc_file_get_executable_path(void) {
} }
buf[len] = '\0'; buf[len] = '\0';
return strdup(buf); return strdup(buf);
#else #elif defined(__APPLE__)
// in practice, we only need this feature for portable builds, only used on char buf[PATH_MAX];
// Windows, so we don't care implementing it for every platform uint32_t bufsize = PATH_MAX;
// (it's useful to have a working version on Linux for debugging though) if (_NSGetExecutablePath(buf, &bufsize) != 0) {
LOGE("Executable path buffer too small; need %u bytes", bufsize);
return NULL; 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);
#endif #endif
} }

View File

@ -95,9 +95,14 @@ 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 = {

29
app/src/util/env.c Normal file
View File

@ -0,0 +1,29 @@
#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
}

12
app/src/util/env.h Normal file
View File

@ -0,0 +1,12 @@
#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,8 +9,6 @@
#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>
@ -23,8 +21,6 @@
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
@ -47,17 +43,26 @@ 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 _WIN32 #ifdef SC_SOCKET_CLOSE_ON_INTERRUPT
if (sock == INVALID_SOCKET) { if (sock == SC_RAW_SOCKET_NONE) {
return SC_SOCKET_NONE; return SC_SOCKET_NONE;
} }
struct sc_socket_windows *socket = malloc(sizeof(*socket)); struct sc_socket_wrapper *socket = malloc(sizeof(*socket));
if (!socket) { if (!socket) {
LOG_OOM(); LOG_OOM();
closesocket(sock); sc_raw_socket_close(sock);
return SC_SOCKET_NONE; return SC_SOCKET_NONE;
} }
@ -72,9 +77,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 _WIN32 #ifdef SC_SOCKET_CLOSE_ON_INTERRUPT
if (socket == SC_SOCKET_NONE) { if (socket == SC_SOCKET_NONE) {
return INVALID_SOCKET; return SC_RAW_SOCKET_NONE;
} }
return socket->socket; return socket->socket;
@ -83,17 +88,6 @@ 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
@ -248,9 +242,9 @@ net_interrupt(sc_socket socket) {
sc_raw_socket raw_sock = unwrap(socket); sc_raw_socket raw_sock = unwrap(socket);
#ifdef _WIN32 #ifdef SC_SOCKET_CLOSE_ON_INTERRUPT
if (!atomic_flag_test_and_set(&socket->closed)) { if (!atomic_flag_test_and_set(&socket->closed)) {
return !closesocket(raw_sock); return sc_raw_socket_close(raw_sock);
} }
return true; return true;
#else #else
@ -262,15 +256,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 _WIN32 #ifdef SC_SOCKET_CLOSE_ON_INTERRUPT
bool ret = true; bool ret = true;
if (!atomic_flag_test_and_set(&socket->closed)) { if (!atomic_flag_test_and_set(&socket->closed)) {
ret = !closesocket(raw_sock); ret = sc_raw_socket_close(raw_sock);
} }
free(socket); free(socket);
return ret; return ret;
#else #else
return !close(raw_sock); return sc_raw_socket_close(raw_sock);
#endif #endif
} }

View File

@ -7,21 +7,36 @@
#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_windows { typedef struct sc_socket_wrapper {
SOCKET socket; sc_raw_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 int sc_socket; typedef sc_raw_socket 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 meson gcc make sudo dnf install SDL2-devel ffms2-devel libusb1-devel libavdevice-free-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-v2.7`][direct-scrcpy-server] - [`scrcpy-server-v3.0`][direct-scrcpy-server]
<sub>SHA-256: `a23c5659f36c260f105c022d27bcb3eafffa26070e7baa9eda66d01377a1adba`</sub> <sub>SHA-256: `800044c62a94d5fc16f5ab9c86d45b1050eae3eb436514d1b0d2fe2646b894ea`</sub>
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-server-v2.7 [direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-server-v3.0
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,14 +23,20 @@ To control the device without mirroring:
scrcpy --no-video --no-audio scrcpy --no-video --no-audio
``` ```
By default, mouse mode is switched to UHID if video mirroring is disabled (a By default, the mouse is disabled when video playback is turned off.
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 --keyboard=uhid scrcpy --no-video --no-audio --mouse=uhid --keyboard=uhid
scrcpy --no-video --no-audio -K # short version scrcpy --no-video --no-audio -MK # short version
``` ```
To use AOA instead (over USB only): To use AOA instead (over USB only):

View File

@ -461,26 +461,30 @@ meson setup x -Dserver_debugger=true
meson configure x -Dserver_debugger=true meson configure x -Dserver_debugger=true
``` ```
If your device runs Android 8 or below, set the `server_debugger_method` to Then recompile, and run scrcpy.
`old` in addition:
```bash For Android < 11, it will start a debugger on port 5005 on the device and wait:
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
``` ```
In Android Studio, _Run_ > _Debug_ > _Edit configurations..._ On the left, click on For Android >= 11, first find the listening port:
`+`, _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,6 +2,23 @@
## 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:
@ -13,10 +30,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
However, the packaged version is not always the latest release. To install the ### From an install script
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,6 +2,23 @@
## 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
@ -13,7 +30,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 android-platform-tools brew install --cask 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

@ -2,27 +2,32 @@
## Install ## Install
### From the official release
Download the [latest release]: Download the [latest release]:
- [`scrcpy-win64-v2.7.zip`][direct-win64] (64-bit) - [`scrcpy-win64-v3.0.zip`][direct-win64] (64-bit)
<sub>SHA-256: `5910bc18d5a16f42d84185ddc7e16a4cee6a6f5f33451559c1a1d6d0099bd5f5`</sub> <sub>SHA-256: `dfbe8a8fef6535197acc506936bfd59d0aa0427e9b44fb2e5c550eae642f72be`</sub>
- [`scrcpy-win32-v2.7.zip`][direct-win32] (32-bit) - [`scrcpy-win32-v3.0.zip`][direct-win32] (32-bit)
<sub>SHA-256: `ef4daf89d500f33d78b830625536ecb18481429dd94433e7634c824292059d06`</sub> <sub>SHA-256: `7cbf8d7a6ebfdca7b3b161e29a481c11088305f3e0a89d28e8e62f70c7bd0028`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest [latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-win64-v2.7.zip [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/v2.7/scrcpy-win32-v2.7.zip [direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-win32-v3.0.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
``` ```
or [Scoop]: From [Scoop]:
```bash ```bash
@ -30,7 +35,6 @@ 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/v2.7/scrcpy-server-v2.7 PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-server-v3.0
PREBUILT_SERVER_SHA256=a23c5659f36c260f105c022d27bcb3eafffa26070e7baa9eda66d01377a1adba PREBUILT_SERVER_SHA256=800044c62a94d5fc16f5ab9c86d45b1050eae3eb436514d1b0d2fe2646b894ea
echo "[scrcpy] Downloading prebuilt server..." echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

@ -1,5 +1,5 @@
project('scrcpy', 'c', project('scrcpy', 'c',
version: '2.7', version: '3.0',
meson_version: '>= 0.48', meson_version: '>= 0.48',
default_options: [ default_options: [
'c_std=c11', '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('compile_server', type: 'boolean', value: true, description: 'Build the server')
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt 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('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', 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

@ -1,141 +0,0 @@
# 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)/"

View File

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

2
release/.gitignore vendored Normal file
View File

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

5
release/build_common Normal file
View File

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

42
release/build_linux.sh Executable file
View File

@ -0,0 +1,42 @@
#!/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/"

42
release/build_macos.sh Executable file
View File

@ -0,0 +1,42 @@
#!/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/"

14
release/build_server.sh Executable file
View File

@ -0,0 +1,14 @@
#!/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"

52
release/build_windows.sh Executable file
View File

@ -0,0 +1,52 @@
#!/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/"

14
release/generate_checksums.sh Executable file
View File

@ -0,0 +1,14 @@
#!/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"

52
release/package_client.sh Executable file
View File

@ -0,0 +1,52 @@
#!/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'"

10
release/package_server.sh Executable file
View File

@ -0,0 +1,10 @@
#!/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'"

24
release/release.sh Executable file
View File

@ -0,0 +1,24 @@
#!/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"

12
release/test_client.sh Executable file
View File

@ -0,0 +1,12 @@
#!/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

9
release/test_server.sh Executable file
View File

@ -0,0 +1,9 @@
#!/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" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 35 targetSdkVersion 35
versionCode 20700 versionCode 30000
versionName "2.7" versionName "3.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {

View File

@ -12,7 +12,7 @@
set -e set -e
SCRCPY_DEBUG=false SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=2.7 SCRCPY_VERSION_NAME=3.0
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,13 +207,15 @@ public final class CleanUp {
} }
} }
if (displayId != Device.DISPLAY_ID_NONE && Device.isScreenOn(displayId)) { // 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 (powerOffScreen) { if (powerOffScreen) {
Ln.i("Power off screen"); Ln.i("Power off screen");
Device.powerOffScreen(displayId); Device.powerOffScreen(targetDisplayId);
} else if (restoreDisplayPower) { } else if (restoreDisplayPower) {
Ln.i("Restoring display power"); Ln.i("Restoring display power");
Device.setDisplayPower(displayId, true); Device.setDisplayPower(targetDisplayId, true);
} }
} }

View File

@ -21,6 +21,7 @@ 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;
@ -281,7 +282,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 && displayId != Device.DISPLAY_ID_NONE) { if (supportsInputEvents) {
setDisplayPower(msg.getOn()); setDisplayPower(msg.getOn());
} }
break; break;
@ -350,24 +351,47 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
return successCount; return successCount;
} }
private boolean injectTouch(int action, long pointerId, Position position, float pressure, int actionButton, int buttons) { private Pair<Point, Integer> getEventPointAndDisplayId(Position position) {
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();
assert displayData != null : "Cannot receive a touch event without a display"; // 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 = displayData.positionMapper.map(position); Point point;
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 touch event generated for size " + eventSize + " (current size is " + currentSize + ")"); 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) {
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");
@ -421,7 +445,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, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) { if (!Device.injectEvent(downEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) {
return false; return false;
} }
} }
@ -432,7 +456,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, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) { if (!Device.injectEvent(pressEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) {
return false; return false;
} }
@ -446,7 +470,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, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) { if (!Device.injectEvent(releaseEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) {
return false; return false;
} }
@ -454,7 +478,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, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) { if (!Device.injectEvent(upEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) {
return false; return false;
} }
} }
@ -465,27 +489,20 @@ 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, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC); return Device.injectEvent(event, targetDisplayId, 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();
// it hides the field on purpose, to read it with atomic access Pair<Point, Integer> pair = getEventPointAndDisplayId(position);
@SuppressWarnings("checkstyle:HiddenField") if (pair == null) {
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;
@ -497,7 +514,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, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC); return Device.injectEvent(event, targetDisplayId, Device.INJECT_MODE_ASYNC);
} }
/** /**
@ -691,9 +708,12 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
} }
private void setDisplayPower(boolean on) { private void setDisplayPower(boolean on) {
boolean setDisplayPowerOk = Device.setDisplayPower(displayId, 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);
if (setDisplayPowerOk) { if (setDisplayPowerOk) {
keepDisplayPowerOff = !on; // Do not keep display power off for virtual displays: MOD+p must wake up the physical device
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,6 +40,10 @@ 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
} }
@ -127,7 +131,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 (Build.VERSION.SDK_INT >= AndroidVersions.API_35_ANDROID_15) { if (USE_ANDROID_15_DISPLAY_POWER && Build.VERSION.SDK_INT >= AndroidVersions.API_35_ANDROID_15) {
return ServiceManager.getDisplayManager().requestDisplayPower(displayId, on); return ServiceManager.getDisplayManager().requestDisplayPower(displayId, on);
} }

View File

@ -192,6 +192,9 @@ 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 {