Compare commits

...

29 Commits

Author SHA1 Message Date
baa10ed0a3 Update links to 3.0.2 2024-12-04 22:48:27 +01:00
2ed2247e8f Bump version to 3.0.2
The version was not bumped for 3.0.1.
2024-12-04 22:35:25 +01:00
5febb1e9fb Update links to 3.0.1 2024-12-04 21:46:06 +01:00
5c3626ed47 Handle broken pipe errors specifically
Since 9555d3a537, a capture/encoding error
was sometimes logged on exit.
2024-12-04 18:38:23 +01:00
0e473eb005 Reset TCP/IP connection with a '+' prefix
When running scrcpy with --tcpip=xx.xx.xx.xx, to make sure a new working
connection is established, it was first disconnected by a call to:

    adb disconnect <addr>

However, this caused all running instances connected to that address to
be killed. Running several instances of scrcpy on the same device is now
useful with virtual displays, so change the default behavior to NOT
disconnect.

To force a reconnection, a '+' prefix can be added:

    scrcpy --tcpip=+192.168.0.x

Fixes #5562 <https://github.com/Genymobile/scrcpy/issues/5562>
2024-12-04 13:16:51 +01:00
b26b4fb745 Document launchers in virtual displays
Mention how to start a launcher in a virtual display.

Refs #5592 <https://github.com/Genymobile/scrcpy/issues/5592>
2024-12-04 13:10:35 +01:00
9555d3a537 Retry capture on IOException
If the capture fails with an IOException, retry with a lower resolution.

Fixes #5539 <https://github.com/Genymobile/scrcpy/issues/5539>
2024-12-03 23:06:33 +01:00
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
39 changed files with 524 additions and 248 deletions

View File

@ -42,10 +42,10 @@ jobs:
distribution: 'zulu' distribution: 'zulu'
java-version: '17' java-version: '17'
- name: Build scrcpy-server - name: Build
run: release/build_server.sh run: release/build_server.sh
- name: Upload scrcpy-server artifact - name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: scrcpy-server name: scrcpy-server
@ -63,7 +63,7 @@ jobs:
distribution: 'zulu' distribution: 'zulu'
java-version: '17' java-version: '17'
- name: Build scrcpy-server without gradle - name: Build without gradle
run: server/build_without_gradle.sh run: server/build_without_gradle.sh
test-client: test-client:
@ -74,7 +74,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 \
@ -83,36 +82,44 @@ jobs:
- name: Test - name: Test
run: release/test_client.sh run: release/test_client.sh
build-linux: build-linux-x86_64:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check architecture
run: |
arch=$(uname -m)
if [[ "$arch" != x86_64 ]]
then
echo "Unexpected architecture: $arch" >&2
exit 1
fi
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt 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 libv4l-dev
- name: Build linux - name: Build
run: release/build_linux.sh run: release/build_linux.sh x86_64
# upload-artifact does not preserve permissions # upload-artifact does not preserve permissions
- name: Tar - name: Tar
run: | run: |
cd release/work/build-linux cd release/work/build-linux-x86_64
mkdir dist-tar mkdir dist-tar
cd dist-tar cd dist-tar
tar -C .. -cvf dist.tar.gz dist/ tar -C .. -cvf dist.tar.gz dist/
- name: Upload build-linux artifact - name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: build-linux-intermediate name: build-linux-x86_64-intermediate
path: release/work/build-linux/dist-tar/ path: release/work/build-linux-x86_64/dist-tar/
build-win32: build-win32:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -122,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 \
@ -131,7 +137,7 @@ jobs:
- name: Workaround for old meson version run by Github Actions - name: Workaround for old meson version run by Github Actions
run: sed -i 's/^pkg-config/pkgconfig/' cross_win32.txt run: sed -i 's/^pkg-config/pkgconfig/' cross_win32.txt
- name: Build win32 - name: Build
run: release/build_windows.sh 32 run: release/build_windows.sh 32
# upload-artifact does not preserve permissions # upload-artifact does not preserve permissions
@ -142,7 +148,7 @@ jobs:
cd dist-tar cd dist-tar
tar -C .. -cvf dist.tar.gz dist/ tar -C .. -cvf dist.tar.gz dist/
- name: Upload build-win32 artifact - name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: build-win32-intermediate name: build-win32-intermediate
@ -156,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 \
@ -165,7 +170,7 @@ jobs:
- name: Workaround for old meson version run by Github Actions - name: Workaround for old meson version run by Github Actions
run: sed -i 's/^pkg-config/pkgconfig/' cross_win64.txt run: sed -i 's/^pkg-config/pkgconfig/' cross_win64.txt
- name: Build win64 - name: Build
run: release/build_windows.sh 64 run: release/build_windows.sh 64
# upload-artifact does not preserve permissions # upload-artifact does not preserve permissions
@ -176,15 +181,24 @@ jobs:
cd dist-tar cd dist-tar
tar -C .. -cvf dist.tar.gz dist/ tar -C .. -cvf dist.tar.gz dist/
- name: Upload build-win64 artifact - name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: build-win64-intermediate name: build-win64-intermediate
path: release/work/build-win64/dist-tar/ path: release/work/build-win64/dist-tar/
build-macos: build-macos-aarch64:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- name: Check architecture
run: |
arch=$(uname -m)
if [[ "$arch" != arm64 ]]
then
echo "Unexpected architecture: $arch" >&2
exit 1
fi
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -193,27 +207,63 @@ jobs:
brew install meson ninja nasm libiconv zlib automake autoconf \ brew install meson ninja nasm libiconv zlib automake autoconf \
libtool libtool
- name: Build macOS - name: Build
run: release/build_macos.sh run: release/build_macos.sh aarch64
# upload-artifact does not preserve permissions # upload-artifact does not preserve permissions
- name: Tar - name: Tar
run: | run: |
cd release/work/build-macos cd release/work/build-macos-aarch64
mkdir dist-tar mkdir dist-tar
cd dist-tar cd dist-tar
tar -C .. -cvf dist.tar.gz dist/ tar -C .. -cvf dist.tar.gz dist/
- name: Upload build-macos artifact - name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: build-macos-intermediate name: build-macos-aarch64-intermediate
path: release/work/build-macos/dist-tar/ path: release/work/build-macos-aarch64/dist-tar/
package-linux: 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-linux - build-linux-x86_64
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@ -225,25 +275,25 @@ jobs:
name: scrcpy-server name: scrcpy-server
path: release/work/build-server/server/ path: release/work/build-server/server/
- name: Download build-linux - name: Download build-linux-x86_64
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: build-linux-intermediate name: build-linux-x86_64-intermediate
path: release/work/build-linux/dist-tar/ path: release/work/build-linux-x86_64/dist-tar/
# upload-artifact does not preserve permissions # upload-artifact does not preserve permissions
- name: Detar - name: Detar
run: | run: |
cd release/work/build-linux cd release/work/build-linux-x86_64
tar xf dist-tar/dist.tar.gz tar xf dist-tar/dist.tar.gz
- name: Package linux - name: Package
run: release/package_client.sh linux tar.gz run: release/package_client.sh linux-x86_64 tar.gz
- name: Upload linux release - name: Upload release
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: release-linux name: release-linux-x86_64
path: release/output/ path: release/output/
package-win32: package-win32:
@ -273,10 +323,10 @@ jobs:
cd release/work/build-win32 cd release/work/build-win32
tar xf dist-tar/dist.tar.gz tar xf dist-tar/dist.tar.gz
- name: Package win32 - name: Package
run: release/package_client.sh win32 zip run: release/package_client.sh win32 zip
- name: Upload win32 release - name: Upload release
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: release-win32 name: release-win32
@ -309,19 +359,55 @@ jobs:
cd release/work/build-win64 cd release/work/build-win64
tar xf dist-tar/dist.tar.gz tar xf dist-tar/dist.tar.gz
- name: Package win64 - name: Package
run: release/package_client.sh win64 zip run: release/package_client.sh win64 zip
- name: Upload win64 release - name: Upload release
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: release-win64 name: release-win64
path: release/output path: release/output
package-macos: package-macos-aarch64:
needs: needs:
- build-scrcpy-server - build-scrcpy-server
- build-macos - 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 runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@ -336,31 +422,32 @@ jobs:
- name: Download build-macos - name: Download build-macos
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: build-macos-intermediate name: build-macos-x86_64-intermediate
path: release/work/build-macos/dist-tar/ path: release/work/build-macos-x86_64/dist-tar/
# upload-artifact does not preserve permissions # upload-artifact does not preserve permissions
- name: Detar - name: Detar
run: | run: |
cd release/work/build-macos cd release/work/build-macos-x86_64
tar xf dist-tar/dist.tar.gz tar xf dist-tar/dist.tar.gz
- name: Package macos - name: Package
run: release/package_client.sh macos tar.gz run: release/package_client.sh macos-x86_64 tar.gz
- name: Upload macos release - name: Upload release
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: release-macos name: release-macos-x86_64
path: release/output/ path: release/output/
release: release:
needs: needs:
- build-scrcpy-server - build-scrcpy-server
- package-linux - package-linux-x86_64
- package-win32 - package-win32
- package-win64 - package-win64
- package-macos - package-macos-aarch64
- package-macos-x86_64
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
@ -372,10 +459,10 @@ jobs:
name: scrcpy-server name: scrcpy-server
path: release/work/build-server/server/ path: release/work/build-server/server/
- name: Download release-linux - name: Download release-linux-x86_64
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: release-linux name: release-linux-x86_64
path: release/output/ path: release/output/
- name: Download release-win32 - name: Download release-win32
@ -390,10 +477,16 @@ jobs:
name: release-win64 name: release-win64
path: release/output/ path: release/output/
- name: Download release-macos - name: Download release-macos-aarch64
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: release-macos 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/ path: release/output/
- name: Package server - name: Package server

View File

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

View File

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

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',

View File

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

View File

@ -518,13 +518,15 @@ Enable "show touches" on start, restore the initial value on exit.
It only shows physical touches (not clicks from scrcpy). It only shows physical touches (not clicks from scrcpy).
.TP .TP
.BI "\-\-tcpip\fR[=\fIip\fR[:\fIport\fR]] .BI "\-\-tcpip\fR[=[+]\fIip\fR[:\fIport\fR]]
Configure and reconnect the device over TCP/IP. Configure and connect the device over TCP/IP.
If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555). If a destination address is provided, then scrcpy connects to this address before starting. The device must listen on the given TCP port (default is 5555).
If no destination address is provided, then scrcpy attempts to find the IP address and adb port of the current device (typically connected over USB), enables TCP/IP mode if necessary, then connects to this address before starting. If no destination address is provided, then scrcpy attempts to find the IP address and adb port of the current device (typically connected over USB), enables TCP/IP mode if necessary, then connects to this address before starting.
Prefix the address with a '+' to force a reconnection.
.TP .TP
.BI "\-\-time\-limit " seconds .BI "\-\-time\-limit " seconds
Set the maximum mirroring time, in seconds. Set the maximum mirroring time, in seconds.

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;
} }
@ -381,7 +412,7 @@ sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
// "adb connect" always returns successfully (with exit code 0), even in // "adb connect" always returns successfully (with exit code 0), even in
// case of failure. As a workaround, check if its output starts with // case of failure. As a workaround, check if its output starts with
// "connected". // "connected" or "already connected".
char buf[128]; char buf[128];
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf) - 1); ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf) - 1);
sc_pipe_close(pout); sc_pipe_close(pout);
@ -398,7 +429,8 @@ sc_adb_connect(struct sc_intr *intr, const char *ip_port, unsigned flags) {
assert((size_t) r < sizeof(buf)); assert((size_t) r < sizeof(buf));
buf[r] = '\0'; buf[r] = '\0';
ok = !strncmp("connected", buf, sizeof("connected") - 1); ok = !strncmp("connected", buf, sizeof("connected") - 1)
|| !strncmp("already connected", buf, sizeof("already connected") - 1);
if (!ok && !(flags & SC_ADB_NO_STDERR)) { if (!ok && !(flags & SC_ADB_NO_STDERR)) {
// "adb connect" also prints errors to stdout. Since we capture it, // "adb connect" also prints errors to stdout. Since we capture it,
// re-print the error to stderr. // re-print the error to stderr.

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);

View File

@ -860,16 +860,17 @@ static const struct sc_option options[] = {
{ {
.longopt_id = OPT_TCPIP, .longopt_id = OPT_TCPIP,
.longopt = "tcpip", .longopt = "tcpip",
.argdesc = "ip[:port]", .argdesc = "[+]ip[:port]",
.optional_arg = true, .optional_arg = true,
.text = "Configure and reconnect the device over TCP/IP.\n" .text = "Configure and connect the device over TCP/IP.\n"
"If a destination address is provided, then scrcpy connects to " "If a destination address is provided, then scrcpy connects to "
"this address before starting. The device must listen on the " "this address before starting. The device must listen on the "
"given TCP port (default is 5555).\n" "given TCP port (default is 5555).\n"
"If no destination address is provided, then scrcpy attempts " "If no destination address is provided, then scrcpy attempts "
"to find the IP address of the current device (typically " "to find the IP address of the current device (typically "
"connected over USB), enables TCP/IP mode, then connects to " "connected over USB), enables TCP/IP mode, then connects to "
"this address before starting.", "this address before starting.\n"
"Prefix the address with a '+' to force a reconnection.",
}, },
{ {
.longopt_id = OPT_TIME_LIMIT, .longopt_id = OPT_TIME_LIMIT,

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");
@ -497,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;
} }
@ -512,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;
} }
@ -833,11 +829,14 @@ sc_server_switch_to_tcpip(struct sc_server *server, const char *serial) {
} }
static bool static bool
sc_server_connect_to_tcpip(struct sc_server *server, const char *ip_port) { sc_server_connect_to_tcpip(struct sc_server *server, const char *ip_port,
bool disconnect) {
struct sc_intr *intr = &server->intr; struct sc_intr *intr = &server->intr;
// Error expected if not connected, do not report any error if (disconnect) {
sc_adb_disconnect(intr, ip_port, SC_ADB_SILENT); // Error expected if not connected, do not report any error
sc_adb_disconnect(intr, ip_port, SC_ADB_SILENT);
}
LOGI("Connecting to %s...", ip_port); LOGI("Connecting to %s...", ip_port);
@ -853,7 +852,7 @@ sc_server_connect_to_tcpip(struct sc_server *server, const char *ip_port) {
static bool static bool
sc_server_configure_tcpip_known_address(struct sc_server *server, sc_server_configure_tcpip_known_address(struct sc_server *server,
const char *addr) { const char *addr, bool disconnect) {
// Append ":5555" if no port is present // Append ":5555" if no port is present
bool contains_port = strchr(addr, ':'); bool contains_port = strchr(addr, ':');
char *ip_port = contains_port ? strdup(addr) char *ip_port = contains_port ? strdup(addr)
@ -864,7 +863,7 @@ sc_server_configure_tcpip_known_address(struct sc_server *server,
} }
server->serial = ip_port; server->serial = ip_port;
return sc_server_connect_to_tcpip(server, ip_port); return sc_server_connect_to_tcpip(server, ip_port, disconnect);
} }
static bool static bool
@ -889,7 +888,7 @@ sc_server_configure_tcpip_unknown_address(struct sc_server *server,
} }
server->serial = ip_port; server->serial = ip_port;
return sc_server_connect_to_tcpip(server, ip_port); return sc_server_connect_to_tcpip(server, ip_port, false);
} }
static void static void
@ -976,7 +975,13 @@ run_server(void *data) {
sc_adb_device_destroy(&device); sc_adb_device_destroy(&device);
} }
} else { } else {
ok = sc_server_configure_tcpip_known_address(server, params->tcpip_dst); // If the user passed a '+' (--tcpip=+ip), then disconnect first
const char *tcpip_dst = params->tcpip_dst;
bool plus = tcpip_dst[0] == '+';
if (plus) {
++tcpip_dst;
}
ok = sc_server_configure_tcpip_known_address(server, tcpip_dst, plus);
if (!ok) { if (!ok) {
goto error_connection_failed; goto error_connection_failed;
} }
@ -1153,4 +1158,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);
#elif defined(__APPLE__)
char buf[PATH_MAX];
uint32_t bufsize = PATH_MAX;
if (_NSGetExecutablePath(buf, &bufsize) != 0) {
LOGE("Executable path buffer too small; need %u bytes", bufsize);
return NULL;
}
return realpath(buf, NULL);
#else #else
// in practice, we only need this feature for portable builds, only used on // "_" is often used to store the full path of the command being executed
// Windows, so we don't care implementing it for every platform char *path = getenv("_");
// (it's useful to have a working version on Linux for debugging though) if (!path) {
return NULL; 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)...");
unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR; if (sc_adb_init()) {
// uninterruptible (intr == NULL), but in practice it's very quick unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR;
sc_adb_kill_server(NULL, flags); // uninterruptible (intr == NULL), but in practice it's very quick
sc_adb_kill_server(NULL, flags);
sc_adb_destroy();
} else {
LOGW("Could not call adb executable, adb server not killed");
}
#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

@ -233,10 +233,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server #### Option 2: Use prebuilt server
- [`scrcpy-server-v3.0`][direct-scrcpy-server] - [`scrcpy-server-v3.0.2`][direct-scrcpy-server]
<sub>SHA-256: `800044c62a94d5fc16f5ab9c86d45b1050eae3eb436514d1b0d2fe2646b894ea`</sub> <sub>SHA-256: `e19fe024bfa3367809494407ad6ca809a6f6e77dac95e99f85ba75144e0ba35d`</sub>
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-server-v3.0 [direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-server-v3.0.2
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

@ -85,6 +85,12 @@ scrcpy --tcpip=192.168.1.1 # default port is 5555
scrcpy --tcpip=192.168.1.1:5555 scrcpy --tcpip=192.168.1.1:5555
``` ```
Prefix the address with a '+' to force a reconnection:
```bash
scrcpy --tcpip=+192.168.1.1
```
### Manual ### Manual

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

@ -6,11 +6,11 @@
Download a static build of the [latest release]: Download a static build of the [latest release]:
- [`scrcpy-linux-v3.0.tar.gz`][direct-linux] (x86_64) - [`scrcpy-linux-x86_64-v3.0.2.tar.gz`][direct-linux-x86_64] (x86_64)
<sub>SHA-256: `06cb74e22f758228c944cea048b78e42b2925c2affe2b5aca901cfd6a649e503`</sub> <sub>SHA-256: `20b69dcd379bb7d7208bf1e4858cf04162fc856697be0e6c03863d7b3c1e734a`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest [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 [direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-linux-x86_64-v3.0.2.tar.gz
and extract it. and extract it.

View File

@ -6,11 +6,15 @@
Download a static build of the [latest release]: Download a static build of the [latest release]:
- [`scrcpy-macos-v3.0.tar.gz`][direct-macos] (arm64) - [`scrcpy-macos-aarch64-v3.0.2.tar.gz`][direct-macos-aarch64] (aarch64)
<sub>SHA-256: `5db9821918537eb3aaf0333cdd05baf85babdd851972d5f1b71f86da0530b4bf`</sub> <sub>SHA-256: `811ba2f4e856146bdd161e24c3490d78efbec2339ca783fac791d041c0aecfb6`</sub>
- [`scrcpy-macos-x86_64-v3.0.2.tar.gz`][direct-macos-x86_64] (x86_64)
<sub>SHA-256: `8effff54dca3a3e46eaaec242771a13a7f81af2e18670b3d0d8ed6b461bb4f79`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest [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 [direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-macos-aarch64-v3.0.2.tar.gz
[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-macos-x86_64-v3.0.2.tar.gz
and extract it. and extract it.

View File

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

View File

@ -6,14 +6,14 @@
Download the [latest release]: Download the [latest release]:
- [`scrcpy-win64-v3.0.zip`][direct-win64] (64-bit) - [`scrcpy-win64-v3.0.2.zip`][direct-win64] (64-bit)
<sub>SHA-256: `dfbe8a8fef6535197acc506936bfd59d0aa0427e9b44fb2e5c550eae642f72be`</sub> <sub>SHA-256: `f0de59f5d46127c87cd822d39d6665e016b86db4cd048101b262f6adb6766832`</sub>
- [`scrcpy-win32-v3.0.zip`][direct-win32] (32-bit) - [`scrcpy-win32-v3.0.2.zip`][direct-win32] (32-bit)
<sub>SHA-256: `7cbf8d7a6ebfdca7b3b161e29a481c11088305f3e0a89d28e8e62f70c7bd0028`</sub> <sub>SHA-256: `8db8d4984d642012c55802de71f507f8ff9f68a8cfed456d7a1982d47e065f64`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest [latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-win64-v3.0.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-win64-v3.0.2.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.0/scrcpy-win32-v3.0.zip [direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-win32-v3.0.2.zip
and extract it. and extract it.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@
set -e set -e
SCRCPY_DEBUG=false SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=3.0 SCRCPY_VERSION_NAME=3.0.2
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;
if (point == null) { int targetDisplayId;
if (Ln.isEnabled(Ln.Level.VERBOSE)) { if (displayData != null) {
Size eventSize = position.getScreenSize(); point = displayData.positionMapper.map(position);
Size currentSize = displayData.positionMapper.getVideoSize(); if (point == null) {
Ln.v("Ignore touch event generated for size " + eventSize + " (current size is " + currentSize + ")"); if (Ln.isEnabled(Ln.Level.VERBOSE)) {
Size eventSize = position.getScreenSize();
Size currentSize = displayData.positionMapper.getVideoSize();
Ln.v("Ignore positional event generated for size " + eventSize + " (current size is " + currentSize + ")");
}
return null;
} }
targetDisplayId = displayData.virtualDisplayId;
} else {
// No display, use the raw coordinates
point = position.getPoint();
targetDisplayId = displayId;
}
return Pair.create(point, targetDisplayId);
}
private boolean injectTouch(int action, long pointerId, Position position, float pressure, int actionButton, int buttons) {
long now = SystemClock.uptimeMillis();
Pair<Point, Integer> pair = getEventPointAndDisplayId(position);
if (pair == null) {
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

@ -72,4 +72,8 @@ public final class IO {
Throwable cause = e.getCause(); Throwable cause = e.getCause();
return cause instanceof ErrnoException && ((ErrnoException) cause).errno == OsConstants.EPIPE; return cause instanceof ErrnoException && ((ErrnoException) cause).errno == OsConstants.EPIPE;
} }
public static boolean isBrokenPipe(Exception e) {
return e instanceof IOException && isBrokenPipe((IOException) e);
}
} }

View File

@ -112,8 +112,12 @@ public class SurfaceEncoder implements AsyncProcessor {
// The capture might have been closed internally (for example if the camera is disconnected) // The capture might have been closed internally (for example if the camera is disconnected)
alive = !stopped.get() && !capture.isClosed(); alive = !stopped.get() && !capture.isClosed();
} }
} catch (IllegalStateException | IllegalArgumentException e) { } catch (IllegalStateException | IllegalArgumentException | IOException e) {
Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage()); if (IO.isBrokenPipe(e)) {
// Do not retry on broken pipe, which is expected on close because the socket is closed by the client
throw e;
}
Ln.e("Capture/encoding error: " + e.getClass().getName() + ": " + e.getMessage());
if (!prepareRetry(size)) { if (!prepareRetry(size)) {
throw e; throw e;
} }

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 {