Compare commits
No commits in common. "master" and "virtual_display" have entirely different histories.
master
...
virtual_di
421
.github/workflows/release.yml
vendored
421
.github/workflows/release.yml
vendored
@ -6,15 +6,11 @@ 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:
|
||||||
test-scrcpy-server:
|
build-scrcpy-server:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
GRADLE: gradle # use native gradle instead of ./gradlew in scripts
|
GRADLE: gradle # use native gradle instead of ./gradlew in release.mk
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@ -26,45 +22,16 @@ jobs:
|
|||||||
java-version: '17'
|
java-version: '17'
|
||||||
|
|
||||||
- name: Test scrcpy-server
|
- name: Test scrcpy-server
|
||||||
run: release/test_server.sh
|
run: make -f release.mk test-server
|
||||||
|
|
||||||
build-scrcpy-server:
|
- name: Build scrcpy-server
|
||||||
runs-on: ubuntu-latest
|
run: make -f release.mk build-server
|
||||||
env:
|
|
||||||
GRADLE: gradle # use native gradle instead of ./gradlew in scripts
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup JDK
|
- name: Upload scrcpy-server artifact
|
||||||
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: release/work/build-server/server/scrcpy-server
|
path: 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
|
||||||
@ -74,52 +41,18 @@ jobs:
|
|||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
sudo apt update
|
||||||
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
||||||
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
||||||
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev
|
||||||
libv4l-dev
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: release/test_client.sh
|
|
||||||
|
|
||||||
build-linux-x86_64:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check architecture
|
|
||||||
run: |
|
|
||||||
arch=$(uname -m)
|
|
||||||
if [[ "$arch" != x86_64 ]]
|
|
||||||
then
|
|
||||||
echo "Unexpected architecture: $arch" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
|
||||||
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
|
||||||
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
|
||||||
libv4l-dev
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: release/build_linux.sh x86_64
|
|
||||||
|
|
||||||
# upload-artifact does not preserve permissions
|
|
||||||
- name: Tar
|
|
||||||
run: |
|
run: |
|
||||||
cd release/work/build-linux-x86_64
|
meson setup d -Db_sanitize=address,undefined
|
||||||
mkdir dist-tar
|
|
||||||
cd dist-tar
|
|
||||||
tar -C .. -cvf dist.tar.gz dist/
|
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Test
|
||||||
uses: actions/upload-artifact@v4
|
run: |
|
||||||
with:
|
meson test -Cd
|
||||||
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
|
||||||
@ -129,6 +62,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
sudo apt update
|
||||||
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
||||||
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
||||||
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
||||||
@ -137,22 +71,14 @@ jobs:
|
|||||||
- name: Workaround for old meson version run by Github Actions
|
- name: Workaround for old meson version run by Github Actions
|
||||||
run: sed -i 's/^pkg-config/pkgconfig/' cross_win32.txt
|
run: sed -i 's/^pkg-config/pkgconfig/' cross_win32.txt
|
||||||
|
|
||||||
- name: Build
|
- name: Build scrcpy win32
|
||||||
run: release/build_windows.sh 32
|
run: make -f release.mk build-win32
|
||||||
|
|
||||||
# upload-artifact does not preserve permissions
|
- name: Upload build-win32 artifact
|
||||||
- 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: release/work/build-win32/dist-tar/
|
path: build-win32/dist/
|
||||||
|
|
||||||
build-win64:
|
build-win64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -162,6 +88,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
sudo apt update
|
||||||
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
sudo apt install -y meson ninja-build nasm ffmpeg libsdl2-2.0-0 \
|
||||||
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
libsdl2-dev libavcodec-dev libavdevice-dev libavformat-dev \
|
||||||
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
libavutil-dev libswresample-dev libusb-1.0-0 libusb-1.0-0-dev \
|
||||||
@ -170,137 +97,24 @@ jobs:
|
|||||||
- name: Workaround for old meson version run by Github Actions
|
- name: Workaround for old meson version run by Github Actions
|
||||||
run: sed -i 's/^pkg-config/pkgconfig/' cross_win64.txt
|
run: sed -i 's/^pkg-config/pkgconfig/' cross_win64.txt
|
||||||
|
|
||||||
- name: Build
|
- name: Build scrcpy win64
|
||||||
run: release/build_windows.sh 64
|
run: make -f release.mk build-win64
|
||||||
|
|
||||||
# upload-artifact does not preserve permissions
|
- name: Upload build-win64 artifact
|
||||||
- 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: release/work/build-win64/dist-tar/
|
path: build-win64/dist/
|
||||||
|
|
||||||
build-macos-aarch64:
|
package:
|
||||||
runs-on: macos-latest
|
|
||||||
steps:
|
|
||||||
- name: Check architecture
|
|
||||||
run: |
|
|
||||||
arch=$(uname -m)
|
|
||||||
if [[ "$arch" != arm64 ]]
|
|
||||||
then
|
|
||||||
echo "Unexpected architecture: $arch" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
brew install meson ninja nasm libiconv zlib automake autoconf \
|
|
||||||
libtool
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: release/build_macos.sh aarch64
|
|
||||||
|
|
||||||
# upload-artifact does not preserve permissions
|
|
||||||
- name: Tar
|
|
||||||
run: |
|
|
||||||
cd release/work/build-macos-aarch64
|
|
||||||
mkdir dist-tar
|
|
||||||
cd dist-tar
|
|
||||||
tar -C .. -cvf dist.tar.gz dist/
|
|
||||||
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: build-macos-aarch64-intermediate
|
|
||||||
path: release/work/build-macos-aarch64/dist-tar/
|
|
||||||
|
|
||||||
build-macos-x86_64:
|
|
||||||
runs-on: macos-13
|
|
||||||
steps:
|
|
||||||
- name: Check architecture
|
|
||||||
run: |
|
|
||||||
arch=$(uname -m)
|
|
||||||
if [[ "$arch" != x86_64 ]]
|
|
||||||
then
|
|
||||||
echo "Unexpected architecture: $arch" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: brew install meson ninja nasm libiconv zlib automake
|
|
||||||
# autoconf and libtool are already installed on macos-13
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: release/build_macos.sh x86_64
|
|
||||||
|
|
||||||
# upload-artifact does not preserve permissions
|
|
||||||
- name: Tar
|
|
||||||
run: |
|
|
||||||
cd release/work/build-macos-x86_64
|
|
||||||
mkdir dist-tar
|
|
||||||
cd dist-tar
|
|
||||||
tar -C .. -cvf dist.tar.gz dist/
|
|
||||||
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: build-macos-x86_64-intermediate
|
|
||||||
path: release/work/build-macos-x86_64/dist-tar/
|
|
||||||
|
|
||||||
package-linux-x86_64:
|
|
||||||
needs:
|
|
||||||
- build-scrcpy-server
|
|
||||||
- build-linux-x86_64
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Download scrcpy-server
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: scrcpy-server
|
|
||||||
path: release/work/build-server/server/
|
|
||||||
|
|
||||||
- name: Download build-linux-x86_64
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: build-linux-x86_64-intermediate
|
|
||||||
path: release/work/build-linux-x86_64/dist-tar/
|
|
||||||
|
|
||||||
# upload-artifact does not preserve permissions
|
|
||||||
- name: Detar
|
|
||||||
run: |
|
|
||||||
cd release/work/build-linux-x86_64
|
|
||||||
tar xf dist-tar/dist.tar.gz
|
|
||||||
|
|
||||||
- name: Package
|
|
||||||
run: release/package_client.sh linux-x86_64 tar.gz
|
|
||||||
|
|
||||||
- name: Upload release
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: release-linux-x86_64
|
|
||||||
path: release/output/
|
|
||||||
|
|
||||||
package-win32:
|
|
||||||
needs:
|
needs:
|
||||||
- build-scrcpy-server
|
- build-scrcpy-server
|
||||||
- build-win32
|
- build-win32
|
||||||
|
- 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
|
||||||
@ -309,194 +123,25 @@ jobs:
|
|||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: scrcpy-server
|
name: scrcpy-server
|
||||||
path: release/work/build-server/server/
|
path: 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: release/work/build-win32/dist-tar/
|
path: build-win32/dist/
|
||||||
|
|
||||||
# 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: release/work/build-win64/dist-tar/
|
path: build-win64/dist/
|
||||||
|
|
||||||
# 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: release/package_client.sh win64 zip
|
run: make -f release.mk package
|
||||||
|
|
||||||
- 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/output
|
path: release-${{ env.VERSION }}
|
||||||
|
2
LICENSE
2
LICENSE
@ -188,7 +188,7 @@
|
|||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
Copyright (C) 2018 Genymobile
|
||||||
Copyright (C) 2018-2025 Romain Vimont
|
Copyright (C) 2018-2024 Romain Vimont
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
19
README.md
19
README.md
@ -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.1)
|
# scrcpy (v2.7)
|
||||||
|
|
||||||
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|
||||||
|
|
||||||
@ -74,20 +74,10 @@ Note that USB debugging is not required to run scrcpy in [OTG mode](doc/otg.md).
|
|||||||
## Get the app
|
## Get the app
|
||||||
|
|
||||||
- [Linux](doc/linux.md)
|
- [Linux](doc/linux.md)
|
||||||
- [Windows](doc/windows.md) (read [how to run](doc/windows.md#run))
|
- [Windows](doc/windows.md)
|
||||||
- [macOS](doc/macos.md)
|
- [macOS](doc/macos.md)
|
||||||
|
|
||||||
|
|
||||||
## Must-know tips
|
|
||||||
|
|
||||||
- [Reducing resolution](doc/video.md#size) may greatly improve performance
|
|
||||||
(`scrcpy -m1024`)
|
|
||||||
- [_Right-click_](doc/mouse.md#mouse-bindings) triggers `BACK`
|
|
||||||
- [_Middle-click_](doc/mouse.md#mouse-bindings) triggers `HOME`
|
|
||||||
- <kbd>Alt</kbd>+<kbd>f</kbd> toggles [fullscreen](doc/window.md#fullscreen)
|
|
||||||
- There are many other [shortcuts](doc/shortcuts.md)
|
|
||||||
|
|
||||||
|
|
||||||
## Usage examples
|
## Usage examples
|
||||||
|
|
||||||
There are a lot of options, [documented](#user-documentation) in separate pages.
|
There are a lot of options, [documented](#user-documentation) in separate pages.
|
||||||
@ -151,7 +141,7 @@ documented in the following pages:
|
|||||||
- [Device](doc/device.md)
|
- [Device](doc/device.md)
|
||||||
- [Window](doc/window.md)
|
- [Window](doc/window.md)
|
||||||
- [Recording](doc/recording.md)
|
- [Recording](doc/recording.md)
|
||||||
- [Virtual display](doc/virtual_display.md)
|
- [Virtual display](doc/virtual_displays.md)
|
||||||
- [Tunnels](doc/tunnels.md)
|
- [Tunnels](doc/tunnels.md)
|
||||||
- [OTG](doc/otg.md)
|
- [OTG](doc/otg.md)
|
||||||
- [Camera](doc/camera.md)
|
- [Camera](doc/camera.md)
|
||||||
@ -191,7 +181,6 @@ to your problem immediately.
|
|||||||
You can also use:
|
You can also use:
|
||||||
|
|
||||||
- Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy)
|
- Reddit: [`r/scrcpy`](https://www.reddit.com/r/scrcpy)
|
||||||
- BlueSky: [`@scrcpy.bsky.social`](https://bsky.app/profile/scrcpy.bsky.social)
|
|
||||||
- Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app)
|
- Twitter: [`@scrcpy_app`](https://twitter.com/scrcpy_app)
|
||||||
|
|
||||||
|
|
||||||
@ -210,7 +199,7 @@ work][donate]:
|
|||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
Copyright (C) 2018 Genymobile
|
Copyright (C) 2018 Genymobile
|
||||||
Copyright (C) 2018-2025 Romain Vimont
|
Copyright (C) 2018-2024 Romain Vimont
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -2,7 +2,6 @@ _scrcpy() {
|
|||||||
local cur prev words cword
|
local cur prev words cword
|
||||||
local opts="
|
local opts="
|
||||||
--always-on-top
|
--always-on-top
|
||||||
--angle
|
|
||||||
--audio-bit-rate=
|
--audio-bit-rate=
|
||||||
--audio-buffer=
|
--audio-buffer=
|
||||||
--audio-codec=
|
--audio-codec=
|
||||||
@ -18,10 +17,10 @@ _scrcpy() {
|
|||||||
--camera-fps=
|
--camera-fps=
|
||||||
--camera-high-speed
|
--camera-high-speed
|
||||||
--camera-size=
|
--camera-size=
|
||||||
--capture-orientation=
|
|
||||||
--crop=
|
--crop=
|
||||||
-d --select-usb
|
-d --select-usb
|
||||||
--disable-screensaver
|
--disable-screensaver
|
||||||
|
--display-buffer=
|
||||||
--display-id=
|
--display-id=
|
||||||
--display-orientation=
|
--display-orientation=
|
||||||
-e --select-tcpip
|
-e --select-tcpip
|
||||||
@ -39,6 +38,8 @@ _scrcpy() {
|
|||||||
--list-cameras
|
--list-cameras
|
||||||
--list-displays
|
--list-displays
|
||||||
--list-encoders
|
--list-encoders
|
||||||
|
--lock-video-orientation
|
||||||
|
--lock-video-orientation=
|
||||||
-m --max-size=
|
-m --max-size=
|
||||||
-M
|
-M
|
||||||
--max-fps=
|
--max-fps=
|
||||||
@ -57,8 +58,6 @@ _scrcpy() {
|
|||||||
--no-mipmaps
|
--no-mipmaps
|
||||||
--no-mouse-hover
|
--no-mouse-hover
|
||||||
--no-power-on
|
--no-power-on
|
||||||
--no-vd-destroy-content
|
|
||||||
--no-vd-system-decorations
|
|
||||||
--no-video
|
--no-video
|
||||||
--no-video-playback
|
--no-video-playback
|
||||||
--orientation=
|
--orientation=
|
||||||
@ -79,7 +78,6 @@ _scrcpy() {
|
|||||||
--rotation=
|
--rotation=
|
||||||
-s --serial=
|
-s --serial=
|
||||||
-S --turn-screen-off
|
-S --turn-screen-off
|
||||||
--screen-off-timeout=
|
|
||||||
--shortcut-mod=
|
--shortcut-mod=
|
||||||
--start-app=
|
--start-app=
|
||||||
-t --show-touches
|
-t --show-touches
|
||||||
@ -92,7 +90,6 @@ _scrcpy() {
|
|||||||
--v4l2-sink=
|
--v4l2-sink=
|
||||||
-v --version
|
-v --version
|
||||||
-V --verbosity=
|
-V --verbosity=
|
||||||
--video-buffer=
|
|
||||||
--video-codec=
|
--video-codec=
|
||||||
--video-codec-options=
|
--video-codec-options=
|
||||||
--video-encoder=
|
--video-encoder=
|
||||||
@ -140,10 +137,6 @@ _scrcpy() {
|
|||||||
COMPREPLY=($(compgen -W 'disabled uhid aoa' -- "$cur"))
|
COMPREPLY=($(compgen -W 'disabled uhid aoa' -- "$cur"))
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
--capture-orientation)
|
|
||||||
COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270 @0 @90 @180 @270 @flip0 @flip90 @flip180 @flip270' -- "$cur"))
|
|
||||||
return
|
|
||||||
;;
|
|
||||||
--orientation|--display-orientation)
|
--orientation|--display-orientation)
|
||||||
COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
|
COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
|
||||||
return
|
return
|
||||||
@ -152,6 +145,10 @@ _scrcpy() {
|
|||||||
COMPREPLY=($(compgen -W '0 90 180 270' -- "$cur"))
|
COMPREPLY=($(compgen -W '0 90 180 270' -- "$cur"))
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
|
--lock-video-orientation)
|
||||||
|
COMPREPLY=($(compgen -W 'unlocked initial 0 90 180 270' -- "$cur"))
|
||||||
|
return
|
||||||
|
;;
|
||||||
--pause-on-exit)
|
--pause-on-exit)
|
||||||
COMPREPLY=($(compgen -W 'true false if-error' -- "$cur"))
|
COMPREPLY=($(compgen -W 'true false if-error' -- "$cur"))
|
||||||
return
|
return
|
||||||
@ -194,9 +191,9 @@ _scrcpy() {
|
|||||||
|--camera-size \
|
|--camera-size \
|
||||||
|--crop \
|
|--crop \
|
||||||
|--display-id \
|
|--display-id \
|
||||||
|
|--display-buffer \
|
||||||
|--max-fps \
|
|--max-fps \
|
||||||
|-m|--max-size \
|
|-m|--max-size \
|
||||||
|--new-display \
|
|
||||||
|-p|--port \
|
|-p|--port \
|
||||||
|--push-target \
|
|--push-target \
|
||||||
|--rotation \
|
|--rotation \
|
||||||
@ -204,7 +201,6 @@ _scrcpy() {
|
|||||||
|--tunnel-port \
|
|--tunnel-port \
|
||||||
|--v4l2-buffer \
|
|--v4l2-buffer \
|
||||||
|--v4l2-sink \
|
|--v4l2-sink \
|
||||||
|--video-buffer \
|
|
||||||
|--video-codec-options \
|
|--video-codec-options \
|
||||||
|--video-encoder \
|
|--video-encoder \
|
||||||
|--tcpip \
|
|--tcpip \
|
||||||
|
@ -9,7 +9,6 @@ local arguments
|
|||||||
|
|
||||||
arguments=(
|
arguments=(
|
||||||
'--always-on-top[Make scrcpy window always on top \(above other windows\)]'
|
'--always-on-top[Make scrcpy window always on top \(above other windows\)]'
|
||||||
'--angle=[Rotate the video content by a custom angle, in degrees]'
|
|
||||||
'--audio-bit-rate=[Encode the audio at the given bit-rate]'
|
'--audio-bit-rate=[Encode the audio at the given bit-rate]'
|
||||||
'--audio-buffer=[Configure the audio buffering delay (in milliseconds)]'
|
'--audio-buffer=[Configure the audio buffering delay (in milliseconds)]'
|
||||||
'--audio-codec=[Select the audio codec]:codec:(opus aac flac raw)'
|
'--audio-codec=[Select the audio codec]:codec:(opus aac flac raw)'
|
||||||
@ -25,10 +24,10 @@ arguments=(
|
|||||||
'--camera-facing=[Select the device camera by its facing direction]:facing:(front back external)'
|
'--camera-facing=[Select the device camera by its facing direction]:facing:(front back external)'
|
||||||
'--camera-fps=[Specify the camera capture frame rate]'
|
'--camera-fps=[Specify the camera capture frame rate]'
|
||||||
'--camera-size=[Specify an explicit camera capture size]'
|
'--camera-size=[Specify an explicit camera capture size]'
|
||||||
'--capture-orientation=[Set the capture video orientation]:orientation:(0 90 180 270 flip0 flip90 flip180 flip270 @0 @90 @180 @270 @flip0 @flip90 @flip180 @flip270)'
|
|
||||||
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
|
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
|
||||||
{-d,--select-usb}'[Use USB device]'
|
{-d,--select-usb}'[Use USB device]'
|
||||||
'--disable-screensaver[Disable screensaver while scrcpy is running]'
|
'--disable-screensaver[Disable screensaver while scrcpy is running]'
|
||||||
|
'--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]'
|
||||||
'--display-id=[Specify the display id to mirror]'
|
'--display-id=[Specify the display id to mirror]'
|
||||||
'--display-orientation=[Set the initial display orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)'
|
'--display-orientation=[Set the initial display orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)'
|
||||||
{-e,--select-tcpip}'[Use TCP/IP device]'
|
{-e,--select-tcpip}'[Use TCP/IP device]'
|
||||||
@ -46,6 +45,7 @@ arguments=(
|
|||||||
'--list-cameras[List cameras available on the device]'
|
'--list-cameras[List cameras available on the device]'
|
||||||
'--list-displays[List displays available on the device]'
|
'--list-displays[List displays available on the device]'
|
||||||
'--list-encoders[List video and audio encoders available on the device]'
|
'--list-encoders[List video and audio encoders available on the device]'
|
||||||
|
'--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 90 180 270)'
|
||||||
{-m,--max-size=}'[Limit both the width and height of the video to value]'
|
{-m,--max-size=}'[Limit both the width and height of the video to value]'
|
||||||
'-M[Use UHID/AOA mouse (same as --mouse=uhid or --mouse=aoa, depending on OTG mode)]'
|
'-M[Use UHID/AOA mouse (same as --mouse=uhid or --mouse=aoa, depending on OTG mode)]'
|
||||||
'--max-fps=[Limit the frame rate of screen capture]'
|
'--max-fps=[Limit the frame rate of screen capture]'
|
||||||
@ -63,8 +63,6 @@ arguments=(
|
|||||||
'--no-mipmaps[Disable the generation of mipmaps]'
|
'--no-mipmaps[Disable the generation of mipmaps]'
|
||||||
'--no-mouse-hover[Do not forward mouse hover events]'
|
'--no-mouse-hover[Do not forward mouse hover events]'
|
||||||
'--no-power-on[Do not power on the device on start]'
|
'--no-power-on[Do not power on the device on start]'
|
||||||
'--no-vd-destroy-content[Disable virtual display "destroy content on removal" flag]'
|
|
||||||
'--no-vd-system-decorations[Disable virtual display system decorations flag]'
|
|
||||||
'--no-video[Disable video forwarding]'
|
'--no-video[Disable video forwarding]'
|
||||||
'--no-video-playback[Disable video playback]'
|
'--no-video-playback[Disable video playback]'
|
||||||
'--orientation=[Set the video orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)'
|
'--orientation=[Set the video orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)'
|
||||||
@ -83,7 +81,6 @@ arguments=(
|
|||||||
'--require-audio=[Make scrcpy fail if audio is enabled but does not work]'
|
'--require-audio=[Make scrcpy fail if audio is enabled but does not work]'
|
||||||
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))'
|
{-s,--serial=}'[The device serial number \(mandatory for multiple devices only\)]:serial:($("${ADB-adb}" devices | awk '\''$2 == "device" {print $1}'\''))'
|
||||||
{-S,--turn-screen-off}'[Turn the device screen off immediately]'
|
{-S,--turn-screen-off}'[Turn the device screen off immediately]'
|
||||||
'--screen-off-timeout=[Set the screen off timeout in seconds]'
|
|
||||||
'--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)'
|
'--shortcut-mod=[\[key1,key2+key3,...\] Specify the modifiers to use for scrcpy shortcuts]:shortcut mod:(lctrl rctrl lalt ralt lsuper rsuper)'
|
||||||
'--start-app=[Start an Android app]'
|
'--start-app=[Start an Android app]'
|
||||||
{-t,--show-touches}'[Show physical touches]'
|
{-t,--show-touches}'[Show physical touches]'
|
||||||
@ -95,7 +92,6 @@ arguments=(
|
|||||||
'--v4l2-sink=[\[\/dev\/videoN\] Output to v4l2loopback device]'
|
'--v4l2-sink=[\[\/dev\/videoN\] Output to v4l2loopback device]'
|
||||||
{-v,--version}'[Print the version of scrcpy]'
|
{-v,--version}'[Print the version of scrcpy]'
|
||||||
{-V,--verbosity=}'[Set the log level]:verbosity:(verbose debug info warn error)'
|
{-V,--verbosity=}'[Set the log level]:verbosity:(verbose debug info warn error)'
|
||||||
'--video-buffer=[Add a buffering delay \(in milliseconds\) before displaying video frames]'
|
|
||||||
'--video-codec=[Select the video codec]:codec:(h264 h265 av1)'
|
'--video-codec=[Select the video codec]:codec:(h264 h265 av1)'
|
||||||
'--video-codec-options=[Set a list of comma-separated key\:type=value options for the device video encoder]'
|
'--video-codec-options=[Set a list of comma-separated key\:type=value options for the device video encoder]'
|
||||||
'--video-encoder=[Use a specific MediaCodec video encoder]'
|
'--video-encoder=[Use a specific MediaCodec video encoder]'
|
||||||
|
@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
|||||||
cd "$DEPS_DIR"
|
cd "$DEPS_DIR"
|
||||||
. common
|
. common
|
||||||
|
|
||||||
VERSION=35.0.2
|
VERSION=35.0.0
|
||||||
FILENAME=platform-tools_r$VERSION-win.zip
|
FILENAME=platform-tools_r$VERSION-windows.zip
|
||||||
PROJECT_DIR=platform-tools-$VERSION-windows
|
PROJECT_DIR=platform-tools-$VERSION
|
||||||
SHA256SUM=2975a3eac0b19182748d64195375ad056986561d994fffbdc64332a516300bb9
|
SHA256SUM=7ab78a8f8b305ae4d0de647d99c43599744de61a0838d3a47bda0cdffefee87e
|
||||||
|
|
||||||
cd "$SOURCES_DIR"
|
cd "$SOURCES_DIR"
|
||||||
|
|
||||||
@ -27,6 +27,6 @@ else
|
|||||||
rmdir "$ZIP_PREFIX"
|
rmdir "$ZIP_PREFIX"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$INSTALL_DIR/adb-windows"
|
mkdir -p "$INSTALL_DIR/$HOST/bin"
|
||||||
cd "$INSTALL_DIR/adb-windows"
|
cd "$INSTALL_DIR/$HOST/bin"
|
||||||
cp -r "$SOURCES_DIR/$PROJECT_DIR"/. "$INSTALL_DIR/adb-windows/"
|
cp -r "$SOURCES_DIR/$PROJECT_DIR"/. "$INSTALL_DIR/$HOST/bin/"
|
@ -1,29 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -ex
|
|
||||||
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
|
||||||
cd "$DEPS_DIR"
|
|
||||||
. common
|
|
||||||
|
|
||||||
VERSION=35.0.2
|
|
||||||
FILENAME=platform-tools_r$VERSION-linux.zip
|
|
||||||
PROJECT_DIR=platform-tools-$VERSION-linux
|
|
||||||
SHA256SUM=acfdcccb123a8718c46c46c059b2f621140194e5ec1ac9d81715be3d6ab6cd0a
|
|
||||||
|
|
||||||
cd "$SOURCES_DIR"
|
|
||||||
|
|
||||||
if [[ -d "$PROJECT_DIR" ]]
|
|
||||||
then
|
|
||||||
echo "$PWD/$PROJECT_DIR" found
|
|
||||||
else
|
|
||||||
get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM"
|
|
||||||
mkdir -p "$PROJECT_DIR"
|
|
||||||
cd "$PROJECT_DIR"
|
|
||||||
ZIP_PREFIX=platform-tools
|
|
||||||
unzip "../$FILENAME" "$ZIP_PREFIX"/adb
|
|
||||||
mv "$ZIP_PREFIX"/* .
|
|
||||||
rmdir "$ZIP_PREFIX"
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$INSTALL_DIR/adb-linux"
|
|
||||||
cd "$INSTALL_DIR/adb-linux"
|
|
||||||
cp -r "$SOURCES_DIR/$PROJECT_DIR"/. "$INSTALL_DIR/adb-linux/"
|
|
@ -1,29 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -ex
|
|
||||||
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
|
||||||
cd "$DEPS_DIR"
|
|
||||||
. common
|
|
||||||
|
|
||||||
VERSION=35.0.2
|
|
||||||
FILENAME=platform-tools_r$VERSION-darwin.zip
|
|
||||||
PROJECT_DIR=platform-tools-$VERSION-darwin
|
|
||||||
SHA256SUM=1820078db90bf21628d257ff052528af1c61bb48f754b3555648f5652fa35d78
|
|
||||||
|
|
||||||
cd "$SOURCES_DIR"
|
|
||||||
|
|
||||||
if [[ -d "$PROJECT_DIR" ]]
|
|
||||||
then
|
|
||||||
echo "$PWD/$PROJECT_DIR" found
|
|
||||||
else
|
|
||||||
get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM"
|
|
||||||
mkdir -p "$PROJECT_DIR"
|
|
||||||
cd "$PROJECT_DIR"
|
|
||||||
ZIP_PREFIX=platform-tools
|
|
||||||
unzip "../$FILENAME" "$ZIP_PREFIX"/adb
|
|
||||||
mv "$ZIP_PREFIX"/* .
|
|
||||||
rmdir "$ZIP_PREFIX"
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$INSTALL_DIR/adb-macos"
|
|
||||||
cd "$INSTALL_DIR/adb-macos"
|
|
||||||
cp -r "$SOURCES_DIR/$PROJECT_DIR"/. "$INSTALL_DIR/adb-macos/"
|
|
@ -1,47 +1,25 @@
|
|||||||
#!/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
|
||||||
|
|
||||||
process_args() {
|
if [[ $# != 1 ]]
|
||||||
if [[ $# != 3 ]]
|
then
|
||||||
then
|
|
||||||
# <host>: win32 or win64
|
# <host>: win32 or win64
|
||||||
# <build_type>: native or cross
|
echo "Syntax: $0 <host>" >&2
|
||||||
# <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 [[ "$BUILD_TYPE" != native && "$BUILD_TYPE" != cross ]]
|
if [[ "$HOST" = win32 ]]
|
||||||
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 cross-build to host: $HOST" >&2
|
echo "Unsupported 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"
|
||||||
@ -59,7 +37,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" | shasum -a256 -c
|
echo "$sum $file" | sha256sum -c
|
||||||
}
|
}
|
||||||
|
|
||||||
get_file() {
|
get_file() {
|
||||||
|
@ -1,68 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -ex
|
|
||||||
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
|
||||||
cd "$DEPS_DIR"
|
|
||||||
. common
|
|
||||||
process_args "$@"
|
|
||||||
|
|
||||||
VERSION=1.5.0
|
|
||||||
FILENAME=dav1d-$VERSION.tar.gz
|
|
||||||
PROJECT_DIR=dav1d-$VERSION
|
|
||||||
SHA256SUM=78b15d9954b513ea92d27f39362535ded2243e1b0924fde39f37a31ebed5f76b
|
|
||||||
|
|
||||||
cd "$SOURCES_DIR"
|
|
||||||
|
|
||||||
if [[ -d "$PROJECT_DIR" ]]
|
|
||||||
then
|
|
||||||
echo "$PWD/$PROJECT_DIR" found
|
|
||||||
else
|
|
||||||
get_file "https://code.videolan.org/videolan/dav1d/-/archive/$VERSION/$FILENAME" "$FILENAME" "$SHA256SUM"
|
|
||||||
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
|
|
||||||
cd "$BUILD_DIR/$PROJECT_DIR"
|
|
||||||
|
|
||||||
if [[ -d "$DIRNAME" ]]
|
|
||||||
then
|
|
||||||
echo "'$PWD/$DIRNAME' already exists, not reconfigured"
|
|
||||||
cd "$DIRNAME"
|
|
||||||
else
|
|
||||||
mkdir "$DIRNAME"
|
|
||||||
cd "$DIRNAME"
|
|
||||||
|
|
||||||
conf=(
|
|
||||||
--prefix="$INSTALL_DIR/$DIRNAME"
|
|
||||||
--libdir=lib
|
|
||||||
-Denable_tests=false
|
|
||||||
-Denable_tools=false
|
|
||||||
# Always build dav1d statically
|
|
||||||
--default-library=static
|
|
||||||
)
|
|
||||||
|
|
||||||
if [[ "$BUILD_TYPE" == cross ]]
|
|
||||||
then
|
|
||||||
case "$HOST" in
|
|
||||||
win32)
|
|
||||||
conf+=(
|
|
||||||
--cross-file="$SOURCES_DIR/$PROJECT_DIR/package/crossfiles/i686-w64-mingw32.meson"
|
|
||||||
)
|
|
||||||
;;
|
|
||||||
|
|
||||||
win64)
|
|
||||||
conf+=(
|
|
||||||
--cross-file="$SOURCES_DIR/$PROJECT_DIR/package/crossfiles/x86_64-w64-mingw32.meson"
|
|
||||||
)
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
|
||||||
echo "Unsupported host: $HOST" >&2
|
|
||||||
exit 1
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
meson setup . "$SOURCES_DIR/$PROJECT_DIR" "${conf[@]}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ninja
|
|
||||||
ninja install
|
|
@ -3,12 +3,11 @@ 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.0.2
|
||||||
FILENAME=ffmpeg-$VERSION.tar.xz
|
FILENAME=ffmpeg-$VERSION.tar.xz
|
||||||
PROJECT_DIR=ffmpeg-$VERSION
|
PROJECT_DIR=ffmpeg-$VERSION
|
||||||
SHA256SUM=40973D44970DBC83EF302B0609F2E74982BE2D85916DD2EE7472D30678A7ABE6
|
SHA256SUM=8646515b638a3ad303e23af6a3587734447cb8fc0a0c064ecdb8e95c4fd8b389
|
||||||
|
|
||||||
cd "$SOURCES_DIR"
|
cd "$SOURCES_DIR"
|
||||||
|
|
||||||
@ -23,121 +22,68 @@ fi
|
|||||||
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
|
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
|
||||||
cd "$BUILD_DIR/$PROJECT_DIR"
|
cd "$BUILD_DIR/$PROJECT_DIR"
|
||||||
|
|
||||||
if [[ -d "$DIRNAME" ]]
|
if [[ "$HOST" = win32 ]]
|
||||||
then
|
then
|
||||||
echo "'$PWD/$DIRNAME' already exists, not reconfigured"
|
ARCH=x86
|
||||||
cd "$DIRNAME"
|
elif [[ "$HOST" = win64 ]]
|
||||||
|
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 PKG_CONFIG_PATH="/opt/homebrew/opt/zlib/lib/pkgconfig"
|
|
||||||
fi
|
|
||||||
|
|
||||||
export PKG_CONFIG_PATH="$INSTALL_DIR/$DIRNAME/lib/pkgconfig:$PKG_CONFIG_PATH"
|
|
||||||
|
|
||||||
conf=(
|
|
||||||
--prefix="$INSTALL_DIR/$DIRNAME"
|
|
||||||
--pkg-config-flags="--static"
|
|
||||||
--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-libdav1d
|
|
||||||
--enable-decoder=h264
|
|
||||||
--enable-decoder=hevc
|
|
||||||
--enable-decoder=av1
|
|
||||||
--enable-decoder=libdav1d
|
|
||||||
--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
|
||||||
esac
|
fi
|
||||||
fi
|
|
||||||
|
|
||||||
"$SOURCES_DIR/$PROJECT_DIR"/configure "${conf[@]}"
|
# -static-libgcc to avoid missing libgcc_s_dw2-1.dll
|
||||||
|
# -static to avoid dynamic dependency to zlib
|
||||||
|
export CFLAGS='-static-libgcc -static'
|
||||||
|
export CXXFLAGS="$CFLAGS"
|
||||||
|
export LDFLAGS='-static-libgcc -static'
|
||||||
|
|
||||||
|
if [[ -d "$HOST" ]]
|
||||||
|
then
|
||||||
|
echo "'$PWD/$HOST' already exists, not reconfigured"
|
||||||
|
cd "$HOST"
|
||||||
|
else
|
||||||
|
mkdir "$HOST"
|
||||||
|
cd "$HOST"
|
||||||
|
|
||||||
|
"$SOURCES_DIR/$PROJECT_DIR"/configure \
|
||||||
|
--prefix="$INSTALL_DIR/$HOST" \
|
||||||
|
--enable-cross-compile \
|
||||||
|
--target-os=mingw32 \
|
||||||
|
--arch="$ARCH" \
|
||||||
|
--cross-prefix="${HOST_TRIPLET}-" \
|
||||||
|
--cc="${HOST_TRIPLET}-gcc" \
|
||||||
|
--extra-cflags="-O2 -fPIC" \
|
||||||
|
--enable-shared \
|
||||||
|
--disable-static \
|
||||||
|
--disable-programs \
|
||||||
|
--disable-doc \
|
||||||
|
--disable-swscale \
|
||||||
|
--disable-postproc \
|
||||||
|
--disable-avfilter \
|
||||||
|
--disable-avdevice \
|
||||||
|
--disable-network \
|
||||||
|
--disable-everything \
|
||||||
|
--enable-swresample \
|
||||||
|
--enable-decoder=h264 \
|
||||||
|
--enable-decoder=hevc \
|
||||||
|
--enable-decoder=av1 \
|
||||||
|
--enable-decoder=pcm_s16le \
|
||||||
|
--enable-decoder=opus \
|
||||||
|
--enable-decoder=aac \
|
||||||
|
--enable-decoder=flac \
|
||||||
|
--enable-decoder=png \
|
||||||
|
--enable-protocol=file \
|
||||||
|
--enable-demuxer=image2 \
|
||||||
|
--enable-parser=png \
|
||||||
|
--enable-zlib \
|
||||||
|
--enable-muxer=matroska \
|
||||||
|
--enable-muxer=mp4 \
|
||||||
|
--enable-muxer=opus \
|
||||||
|
--enable-muxer=flac \
|
||||||
|
--enable-muxer=wav \
|
||||||
|
--disable-vulkan
|
||||||
fi
|
fi
|
||||||
|
|
||||||
make -j
|
make -j
|
||||||
|
@ -3,7 +3,6 @@ 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
|
||||||
@ -26,40 +25,20 @@ cd "$BUILD_DIR/$PROJECT_DIR"
|
|||||||
export CFLAGS='-O2'
|
export CFLAGS='-O2'
|
||||||
export CXXFLAGS="$CFLAGS"
|
export CXXFLAGS="$CFLAGS"
|
||||||
|
|
||||||
if [[ -d "$DIRNAME" ]]
|
if [[ -d "$HOST" ]]
|
||||||
then
|
then
|
||||||
echo "'$PWD/$DIRNAME' already exists, not reconfigured"
|
echo "'$PWD/$HOST' already exists, not reconfigured"
|
||||||
cd "$DIRNAME"
|
cd "$HOST"
|
||||||
else
|
else
|
||||||
mkdir "$DIRNAME"
|
mkdir "$HOST"
|
||||||
cd "$DIRNAME"
|
cd "$HOST"
|
||||||
|
|
||||||
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 "${conf[@]}"
|
"$SOURCES_DIR/$PROJECT_DIR"/configure \
|
||||||
|
--prefix="$INSTALL_DIR/$HOST" \
|
||||||
|
--host="$HOST_TRIPLET" \
|
||||||
|
--enable-shared \
|
||||||
|
--disable-static
|
||||||
fi
|
fi
|
||||||
|
|
||||||
make -j
|
make -j
|
||||||
|
@ -3,12 +3,11 @@ 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.10
|
VERSION=2.30.7
|
||||||
FILENAME=SDL-$VERSION.tar.gz
|
FILENAME=SDL-$VERSION.tar.gz
|
||||||
PROJECT_DIR=SDL-release-$VERSION
|
PROJECT_DIR=SDL-release-$VERSION
|
||||||
SHA256SUM=35a8b9c4f3635d85762b904ac60ca4e0806bff89faeb269caafbe80860d67168
|
SHA256SUM=1578c96f62c9ae36b64e431b2aa0e0b0fd07c275dedbc694afc38e19056688f5
|
||||||
|
|
||||||
cd "$SOURCES_DIR"
|
cd "$SOURCES_DIR"
|
||||||
|
|
||||||
@ -26,54 +25,23 @@ cd "$BUILD_DIR/$PROJECT_DIR"
|
|||||||
export CFLAGS='-O2'
|
export CFLAGS='-O2'
|
||||||
export CXXFLAGS="$CFLAGS"
|
export CXXFLAGS="$CFLAGS"
|
||||||
|
|
||||||
if [[ -d "$DIRNAME" ]]
|
if [[ -d "$HOST" ]]
|
||||||
then
|
then
|
||||||
echo "'$PWD/$HDIRNAME' already exists, not reconfigured"
|
echo "'$PWD/$HOST' already exists, not reconfigured"
|
||||||
cd "$DIRNAME"
|
cd "$HOST"
|
||||||
else
|
else
|
||||||
mkdir "$DIRNAME"
|
mkdir "$HOST"
|
||||||
cd "$DIRNAME"
|
cd "$HOST"
|
||||||
|
|
||||||
conf=(
|
"$SOURCES_DIR/$PROJECT_DIR"/configure \
|
||||||
--prefix="$INSTALL_DIR/$DIRNAME"
|
--prefix="$INSTALL_DIR/$HOST" \
|
||||||
)
|
--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
|
||||||
if [[ "$LINK_TYPE" == shared && "$HOST" == win* ]]
|
${HOST_TRIPLET}-strip "$INSTALL_DIR/$HOST/bin/SDL2.dll"
|
||||||
then
|
|
||||||
${HOST_TRIPLET}-strip "$INSTALL_DIR/$DIRNAME/bin/SDL2.dll"
|
|
||||||
fi
|
|
||||||
|
@ -46,7 +46,6 @@ src = [
|
|||||||
'src/util/acksync.c',
|
'src/util/acksync.c',
|
||||||
'src/util/audiobuf.c',
|
'src/util/audiobuf.c',
|
||||||
'src/util/average.c',
|
'src/util/average.c',
|
||||||
'src/util/env.c',
|
|
||||||
'src/util/file.c',
|
'src/util/file.c',
|
||||||
'src/util/intmap.c',
|
'src/util/intmap.c',
|
||||||
'src/util/intr.c',
|
'src/util/intr.c',
|
||||||
@ -110,22 +109,20 @@ endif
|
|||||||
|
|
||||||
cc = meson.get_compiler('c')
|
cc = meson.get_compiler('c')
|
||||||
|
|
||||||
static = get_option('static')
|
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
dependency('libavformat', version: '>= 57.33', static: static),
|
dependency('libavformat', version: '>= 57.33'),
|
||||||
dependency('libavcodec', version: '>= 57.37', static: static),
|
dependency('libavcodec', version: '>= 57.37'),
|
||||||
dependency('libavutil', static: static),
|
dependency('libavutil'),
|
||||||
dependency('libswresample', static: static),
|
dependency('libswresample'),
|
||||||
dependency('sdl2', version: '>= 2.0.5', static: static),
|
dependency('sdl2', version: '>= 2.0.5'),
|
||||||
]
|
]
|
||||||
|
|
||||||
if v4l2_support
|
if v4l2_support
|
||||||
dependencies += dependency('libavdevice', static: static)
|
dependencies += dependency('libavdevice')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if usb_support
|
if usb_support
|
||||||
dependencies += dependency('libusb-1.0', static: static)
|
dependencies += dependency('libusb-1.0')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if host_machine.system() == 'windows'
|
if host_machine.system() == 'windows'
|
||||||
@ -170,6 +167,9 @@ conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
|
|||||||
# run a server debugger and wait for a client to be attached
|
# 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)
|
||||||
|
|
||||||
|
@ -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.1"
|
VALUE "ProductVersion", "2.7"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
93
app/scrcpy.1
93
app/scrcpy.1
@ -19,10 +19,6 @@ provides display and control of Android devices connected on USB (or over TCP/IP
|
|||||||
.B \-\-always\-on\-top
|
.B \-\-always\-on\-top
|
||||||
Make scrcpy window always on top (above other windows).
|
Make scrcpy window always on top (above other windows).
|
||||||
|
|
||||||
.TP
|
|
||||||
.BI "\-\-angle " degrees
|
|
||||||
Rotate the video content by a custom angle, in degrees (clockwise).
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-audio\-bit\-rate " value
|
.BI "\-\-audio\-bit\-rate " value
|
||||||
Encode the audio at the given bit rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
|
Encode the audio at the given bit rate, expressed in bits/s. Unit suffixes are supported: '\fBK\fR' (x1000) and '\fBM\fR' (x1000000).
|
||||||
@ -97,18 +93,6 @@ Select the camera size by its aspect ratio (+/- 10%).
|
|||||||
|
|
||||||
Possible values are "sensor" (use the camera sensor aspect ratio), "\fInum\fR:\fIden\fR" (e.g. "4:3") and "\fIvalue\fR" (e.g. "1.6").
|
Possible values are "sensor" (use the camera sensor aspect ratio), "\fInum\fR:\fIden\fR" (e.g. "4:3") and "\fIvalue\fR" (e.g. "1.6").
|
||||||
|
|
||||||
.TP
|
|
||||||
.BI "\-\-camera\-facing " facing
|
|
||||||
Select the device camera by its facing direction.
|
|
||||||
|
|
||||||
Possible values are "front", "back" and "external".
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.BI "\-\-camera\-fps " fps
|
|
||||||
Specify the camera capture frame rate.
|
|
||||||
|
|
||||||
If not specified, Android's default frame rate (30 fps) is used.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-camera\-high\-speed
|
.B \-\-camera\-high\-speed
|
||||||
Enable high-speed camera capture mode.
|
Enable high-speed camera capture mode.
|
||||||
@ -122,26 +106,28 @@ Specify the device camera id to mirror.
|
|||||||
The available camera ids can be listed by \fB\-\-list\-cameras\fR.
|
The available camera ids can be listed by \fB\-\-list\-cameras\fR.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-camera\-size " width\fRx\fIheight
|
.BI "\-\-camera\-facing " facing
|
||||||
Specify an explicit camera capture size.
|
Select the device camera by its facing direction.
|
||||||
|
|
||||||
|
Possible values are "front", "back" and "external".
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-capture\-orientation " value
|
.BI "\-\-camera\-fps " fps
|
||||||
Possible values are 0, 90, 180, 270, flip0, flip90, flip180 and flip270, possibly prefixed by '@'.
|
Specify the camera capture frame rate.
|
||||||
|
|
||||||
The number represents the clockwise rotation in degrees; the "flip" keyword applies a horizontal flip before the rotation.
|
If not specified, Android's default frame rate (30 fps) is used.
|
||||||
|
|
||||||
If a leading '@' is passed (@90) for display capture, then the rotation is locked, and is relative to the natural device orientation.
|
.TP
|
||||||
|
.BI "\-\-camera\-size " width\fRx\fIheight
|
||||||
If '@' is passed alone, then the rotation is locked to the initial device orientation.
|
Specify an explicit camera capture size.
|
||||||
|
|
||||||
Default is 0.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
|
.BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
|
||||||
Crop the device screen on the server.
|
Crop the device screen on the server.
|
||||||
|
|
||||||
The values are expressed in the device natural orientation (typically, portrait for a phone, landscape for a tablet).
|
The values are expressed in the device natural orientation (typically, portrait for a phone, landscape for a tablet). Any
|
||||||
|
.B \-\-max\-size
|
||||||
|
value is computed on the cropped size.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-d, \-\-select\-usb
|
.B \-d, \-\-select\-usb
|
||||||
@ -153,6 +139,12 @@ Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR).
|
|||||||
.BI "\-\-disable\-screensaver"
|
.BI "\-\-disable\-screensaver"
|
||||||
Disable screensaver while scrcpy is running.
|
Disable screensaver while scrcpy is running.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-display\-buffer " ms
|
||||||
|
Add a buffering delay (in milliseconds) before displaying. This increases latency to compensate for jitter.
|
||||||
|
|
||||||
|
Default is 0 (no buffering).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-display\-id " id
|
.BI "\-\-display\-id " id
|
||||||
Specify the device display id to mirror.
|
Specify the device display id to mirror.
|
||||||
@ -255,6 +247,16 @@ List video and audio encoders available on the device.
|
|||||||
.B \-\-list\-displays
|
.B \-\-list\-displays
|
||||||
List displays available on the device.
|
List displays available on the device.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB\-\-lock\-video\-orientation\fR[=\fIvalue\fR]
|
||||||
|
Lock capture video orientation to \fIvalue\fR.
|
||||||
|
|
||||||
|
Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 90, 180, and 270. The values represent the clockwise rotation from the natural device orientation, in degrees.
|
||||||
|
|
||||||
|
Default is "unlocked".
|
||||||
|
|
||||||
|
Passing the option without argument is equivalent to passing "initial".
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-m, \-\-max\-size " value
|
.BI "\-m, \-\-max\-size " value
|
||||||
Limit both the width and height of the video to \fIvalue\fR. The other dimension is computed so that the device aspect\-ratio is preserved.
|
Limit both the width and height of the video to \fIvalue\fR. The other dimension is computed so that the device aspect\-ratio is preserved.
|
||||||
@ -318,13 +320,14 @@ Disable video and audio playback on the computer (equivalent to \fB\-\-no\-video
|
|||||||
|
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-new\-display\fR[=[\fIwidth\fRx\fIheight\fR][/\fIdpi\fR]]
|
\fB\-\-new\-display\fR[=[\fIwidth\fRx\fIheight\fR][/\fIdpi\fR]]
|
||||||
Create a new display with the specified resolution and density. If not provided, they default to the main display dimensions and DPI.
|
Create a new display with the specified resolution and density. If not provided, they default to the main display dimensions and DPI, and \fB\-\-max\-size\fR is considered.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
\-\-new\-display=1920x1080
|
\-\-new\-display=1920x1080
|
||||||
\-\-new\-display=1920x1080/420
|
\-\-new\-display=1920x1080/420
|
||||||
\-\-new\-display # main display size and density
|
\-\-new\-display # main display size and density
|
||||||
|
\-\-new\-display -m1920 # scaled to fit a max size of 1920
|
||||||
\-\-new\-display=/240 # main display size and 240 dpi
|
\-\-new\-display=/240 # main display size and 240 dpi
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
@ -369,16 +372,6 @@ Do not forward mouse hover (mouse motion without any clicks) events.
|
|||||||
.B \-\-no\-power\-on
|
.B \-\-no\-power\-on
|
||||||
Do not power on the device on start.
|
Do not power on the device on start.
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-\-no\-vd\-destroy\-content
|
|
||||||
Disable virtual display "destroy content on removal" flag.
|
|
||||||
|
|
||||||
With this option, when the virtual display is closed, the running apps are moved to the main display rather than being destroyed.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-\-no\-vd\-system\-decorations
|
|
||||||
Disable virtual display system decorations flag.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-no\-video
|
.B \-\-no\-video
|
||||||
Disable video forwarding.
|
Disable video forwarding.
|
||||||
@ -524,15 +517,13 @@ 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 connect the device over TCP/IP.
|
Configure and reconnect 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.
|
||||||
@ -563,19 +554,13 @@ Default is "info" for release builds, "debug" for debug builds.
|
|||||||
.BI "\-\-v4l2-sink " /dev/videoN
|
.BI "\-\-v4l2-sink " /dev/videoN
|
||||||
Output to v4l2loopback device.
|
Output to v4l2loopback device.
|
||||||
|
|
||||||
|
It requires to lock the video orientation (see \fB\-\-lock\-video\-orientation\fR).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-v4l2-buffer " ms
|
.BI "\-\-v4l2-buffer " ms
|
||||||
Add a buffering delay (in milliseconds) before pushing frames. This increases latency to compensate for jitter.
|
Add a buffering delay (in milliseconds) before pushing frames. This increases latency to compensate for jitter.
|
||||||
|
|
||||||
This option is similar to \fB\-\-video\-buffer\fR, but specific to V4L2 sink.
|
This option is similar to \fB\-\-display\-buffer\fR, but specific to V4L2 sink.
|
||||||
|
|
||||||
Default is 0 (no buffering).
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.BI "\-\-video\-buffer " ms
|
|
||||||
Add a buffering delay (in milliseconds) before displaying video frames.
|
|
||||||
|
|
||||||
This increases latency to compensate for jitter.
|
|
||||||
|
|
||||||
Default is 0 (no buffering).
|
Default is 0 (no buffering).
|
||||||
|
|
||||||
@ -684,10 +669,6 @@ Pause or re-pause display
|
|||||||
.B MOD+Shift+z
|
.B MOD+Shift+z
|
||||||
Unpause display
|
Unpause display
|
||||||
|
|
||||||
.TP
|
|
||||||
.B MOD+Shift+r
|
|
||||||
Reset video capture/encoding
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B MOD+g
|
.B MOD+g
|
||||||
Resize window to 1:1 (pixel\-perfect)
|
Resize window to 1:1 (pixel\-perfect)
|
||||||
@ -829,7 +810,7 @@ Report bugs to <https://github.com/Genymobile/scrcpy/issues>.
|
|||||||
.SH COPYRIGHT
|
.SH COPYRIGHT
|
||||||
Copyright \(co 2018 Genymobile <https://www.genymobile.com>
|
Copyright \(co 2018 Genymobile <https://www.genymobile.com>
|
||||||
|
|
||||||
Copyright \(co 2018\-2025 Romain Vimont <rom@rom1v.com>
|
Copyright \(co 2018\-2024 Romain Vimont <rom@rom1v.com>
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0.
|
Licensed under the Apache License, Version 2.0.
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#include "adb_device.h"
|
#include "adb_device.h"
|
||||||
#include "adb_parser.h"
|
#include "adb_parser.h"
|
||||||
#include "util/env.h"
|
|
||||||
#include "util/file.h"
|
#include "util/file.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/process_intr.h"
|
#include "util/process_intr.h"
|
||||||
@ -25,45 +24,15 @@
|
|||||||
*/
|
*/
|
||||||
#define SC_ADB_COMMAND(...) { sc_adb_get_executable(), __VA_ARGS__, NULL }
|
#define SC_ADB_COMMAND(...) { sc_adb_get_executable(), __VA_ARGS__, NULL }
|
||||||
|
|
||||||
static char *adb_executable;
|
static const char *adb_executable;
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_init(void) {
|
|
||||||
adb_executable = sc_get_env("ADB");
|
|
||||||
if (adb_executable) {
|
|
||||||
LOGD("Using adb: %s", adb_executable);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !defined(PORTABLE) || defined(_WIN32)
|
|
||||||
adb_executable = strdup("adb");
|
|
||||||
if (!adb_executable) {
|
|
||||||
LOG_OOM();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// For portable builds, use the absolute path to the adb executable
|
|
||||||
// in the same directory as scrcpy (except on Windows, where "adb"
|
|
||||||
// is sufficient)
|
|
||||||
adb_executable = sc_file_get_local_path("adb");
|
|
||||||
if (!adb_executable) {
|
|
||||||
// Error already logged
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGD("Using adb (portable): %s", adb_executable);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_adb_destroy(void) {
|
|
||||||
free(adb_executable);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *
|
const char *
|
||||||
sc_adb_get_executable(void) {
|
sc_adb_get_executable(void) {
|
||||||
|
if (!adb_executable) {
|
||||||
|
adb_executable = getenv("ADB");
|
||||||
|
if (!adb_executable)
|
||||||
|
adb_executable = "adb";
|
||||||
|
}
|
||||||
return adb_executable;
|
return adb_executable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,7 +381,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" or "already connected".
|
// "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);
|
||||||
@ -429,8 +398,7 @@ 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.
|
||||||
@ -771,21 +739,3 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {
|
|||||||
|
|
||||||
return sc_adb_parse_device_ip(buf);
|
return sc_adb_parse_device_ip(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t
|
|
||||||
sc_adb_get_device_sdk_version(struct sc_intr *intr, const char *serial) {
|
|
||||||
char *sdk_version =
|
|
||||||
sc_adb_getprop(intr, serial, "ro.build.version.sdk", SC_ADB_SILENT);
|
|
||||||
if (!sdk_version) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
long value;
|
|
||||||
bool ok = sc_str_parse_integer(sdk_version, &value);
|
|
||||||
free(sdk_version);
|
|
||||||
if (!ok || value < 0 || value > 0xFFFF) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
@ -15,12 +15,6 @@
|
|||||||
|
|
||||||
#define SC_ADB_SILENT (SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR)
|
#define SC_ADB_SILENT (SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR)
|
||||||
|
|
||||||
bool
|
|
||||||
sc_adb_init(void);
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_adb_destroy(void);
|
|
||||||
|
|
||||||
const char *
|
const char *
|
||||||
sc_adb_get_executable(void);
|
sc_adb_get_executable(void);
|
||||||
|
|
||||||
@ -120,10 +114,4 @@ sc_adb_getprop(struct sc_intr *intr, const char *serial, const char *prop,
|
|||||||
char *
|
char *
|
||||||
sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags);
|
sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags);
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the device SDK version.
|
|
||||||
*/
|
|
||||||
uint16_t
|
|
||||||
sc_adb_get_device_sdk_version(struct sc_intr *intr, const char *serial);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -288,7 +288,7 @@ sc_audio_regulator_push(struct sc_audio_regulator *ar, const AVFrame *frame) {
|
|||||||
|
|
||||||
// Enable compensation when the difference exceeds +/- 4ms.
|
// Enable compensation when the difference exceeds +/- 4ms.
|
||||||
// Disable compensation when the difference is lower than +/- 1ms.
|
// Disable compensation when the difference is lower than +/- 1ms.
|
||||||
int threshold = ar->compensation_active
|
int threshold = ar->compensation != 0
|
||||||
? ar->sample_rate / 1000 /* 1ms */
|
? ar->sample_rate / 1000 /* 1ms */
|
||||||
: ar->sample_rate * 4 / 1000; /* 4ms */
|
: ar->sample_rate * 4 / 1000; /* 4ms */
|
||||||
|
|
||||||
@ -309,12 +309,14 @@ sc_audio_regulator_push(struct sc_audio_regulator *ar, const AVFrame *frame) {
|
|||||||
LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32
|
LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32
|
||||||
" compensation=%d", ar->target_buffering, avg, can_read, diff);
|
" compensation=%d", ar->target_buffering, avg, can_read, diff);
|
||||||
|
|
||||||
|
if (diff != ar->compensation) {
|
||||||
int ret = swr_set_compensation(swr_ctx, diff, distance);
|
int ret = swr_set_compensation(swr_ctx, diff, distance);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
LOGW("Resampling compensation failed: %d", ret);
|
LOGW("Resampling compensation failed: %d", ret);
|
||||||
// not fatal
|
// not fatal
|
||||||
} else {
|
} else {
|
||||||
ar->compensation_active = diff != 0;
|
ar->compensation = diff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -390,7 +392,7 @@ sc_audio_regulator_init(struct sc_audio_regulator *ar, size_t sample_size,
|
|||||||
atomic_init(&ar->played, false);
|
atomic_init(&ar->played, false);
|
||||||
atomic_init(&ar->received, false);
|
atomic_init(&ar->received, false);
|
||||||
atomic_init(&ar->underflow, 0);
|
atomic_init(&ar->underflow, 0);
|
||||||
ar->compensation_active = false;
|
ar->compensation = 0;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -44,8 +44,8 @@ struct sc_audio_regulator {
|
|||||||
// Number of silence samples inserted since the last received packet
|
// Number of silence samples inserted since the last received packet
|
||||||
atomic_uint_least32_t underflow;
|
atomic_uint_least32_t underflow;
|
||||||
|
|
||||||
// Non-zero compensation applied (only used by the receiver thread)
|
// Current applied compensation value (only used by the receiver thread)
|
||||||
bool compensation_active;
|
int compensation;
|
||||||
|
|
||||||
// Set to true the first time a sample is received
|
// Set to true the first time a sample is received
|
||||||
atomic_bool received;
|
atomic_bool received;
|
||||||
|
337
app/src/cli.c
337
app/src/cli.c
@ -50,7 +50,6 @@ enum {
|
|||||||
OPT_POWER_OFF_ON_CLOSE,
|
OPT_POWER_OFF_ON_CLOSE,
|
||||||
OPT_V4L2_SINK,
|
OPT_V4L2_SINK,
|
||||||
OPT_DISPLAY_BUFFER,
|
OPT_DISPLAY_BUFFER,
|
||||||
OPT_VIDEO_BUFFER,
|
|
||||||
OPT_V4L2_BUFFER,
|
OPT_V4L2_BUFFER,
|
||||||
OPT_TUNNEL_HOST,
|
OPT_TUNNEL_HOST,
|
||||||
OPT_TUNNEL_PORT,
|
OPT_TUNNEL_PORT,
|
||||||
@ -106,11 +105,6 @@ enum {
|
|||||||
OPT_NEW_DISPLAY,
|
OPT_NEW_DISPLAY,
|
||||||
OPT_LIST_APPS,
|
OPT_LIST_APPS,
|
||||||
OPT_START_APP,
|
OPT_START_APP,
|
||||||
OPT_SCREEN_OFF_TIMEOUT,
|
|
||||||
OPT_CAPTURE_ORIENTATION,
|
|
||||||
OPT_ANGLE,
|
|
||||||
OPT_NO_VD_SYSTEM_DECORATIONS,
|
|
||||||
OPT_NO_VD_DESTROY_CONTENT,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
@ -152,13 +146,6 @@ static const struct sc_option options[] = {
|
|||||||
.longopt = "always-on-top",
|
.longopt = "always-on-top",
|
||||||
.text = "Make scrcpy window always on top (above other windows).",
|
.text = "Make scrcpy window always on top (above other windows).",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.longopt_id = OPT_ANGLE,
|
|
||||||
.longopt = "angle",
|
|
||||||
.argdesc = "degrees",
|
|
||||||
.text = "Rotate the video content by a custom angle, in degrees "
|
|
||||||
"(clockwise).",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_AUDIO_BIT_RATE,
|
.longopt_id = OPT_AUDIO_BIT_RATE,
|
||||||
.longopt = "audio-bit-rate",
|
.longopt = "audio-bit-rate",
|
||||||
@ -256,6 +243,14 @@ static const struct sc_option options[] = {
|
|||||||
"ratio), \"<num>:<den>\" (e.g. \"4:3\") or \"<value>\" (e.g. "
|
"ratio), \"<num>:<den>\" (e.g. \"4:3\") or \"<value>\" (e.g. "
|
||||||
"\"1.6\")."
|
"\"1.6\")."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.longopt_id = OPT_CAMERA_ID,
|
||||||
|
.longopt = "camera-id",
|
||||||
|
.argdesc = "id",
|
||||||
|
.text = "Specify the device camera id to mirror.\n"
|
||||||
|
"The available camera ids can be listed by:\n"
|
||||||
|
" scrcpy --list-cameras",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_CAMERA_FACING,
|
.longopt_id = OPT_CAMERA_FACING,
|
||||||
.longopt = "camera-facing",
|
.longopt = "camera-facing",
|
||||||
@ -263,14 +258,6 @@ static const struct sc_option options[] = {
|
|||||||
.text = "Select the device camera by its facing direction.\n"
|
.text = "Select the device camera by its facing direction.\n"
|
||||||
"Possible values are \"front\", \"back\" and \"external\".",
|
"Possible values are \"front\", \"back\" and \"external\".",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.longopt_id = OPT_CAMERA_FPS,
|
|
||||||
.longopt = "camera-fps",
|
|
||||||
.argdesc = "value",
|
|
||||||
.text = "Specify the camera capture frame rate.\n"
|
|
||||||
"If not specified, Android's default frame rate (30 fps) is "
|
|
||||||
"used.",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_CAMERA_HIGH_SPEED,
|
.longopt_id = OPT_CAMERA_HIGH_SPEED,
|
||||||
.longopt = "camera-high-speed",
|
.longopt = "camera-high-speed",
|
||||||
@ -278,14 +265,6 @@ static const struct sc_option options[] = {
|
|||||||
"This mode is restricted to specific resolutions and frame "
|
"This mode is restricted to specific resolutions and frame "
|
||||||
"rates, listed by --list-camera-sizes.",
|
"rates, listed by --list-camera-sizes.",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.longopt_id = OPT_CAMERA_ID,
|
|
||||||
.longopt = "camera-id",
|
|
||||||
.argdesc = "id",
|
|
||||||
.text = "Specify the device camera id to mirror.\n"
|
|
||||||
"The available camera ids can be listed by:\n"
|
|
||||||
" scrcpy --list-cameras",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_CAMERA_SIZE,
|
.longopt_id = OPT_CAMERA_SIZE,
|
||||||
.longopt = "camera-size",
|
.longopt = "camera-size",
|
||||||
@ -293,21 +272,12 @@ static const struct sc_option options[] = {
|
|||||||
.text = "Specify an explicit camera capture size.",
|
.text = "Specify an explicit camera capture size.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_CAPTURE_ORIENTATION,
|
.longopt_id = OPT_CAMERA_FPS,
|
||||||
.longopt = "capture-orientation",
|
.longopt = "camera-fps",
|
||||||
.argdesc = "value",
|
.argdesc = "value",
|
||||||
.text = "Set the capture video orientation.\n"
|
.text = "Specify the camera capture frame rate.\n"
|
||||||
"Possible values are 0, 90, 180, 270, flip0, flip90, flip180 "
|
"If not specified, Android's default frame rate (30 fps) is "
|
||||||
"and flip270, possibly prefixed by '@'.\n"
|
"used.",
|
||||||
"The number represents the clockwise rotation in degrees; the "
|
|
||||||
"flip\" keyword applies a horizontal flip before the "
|
|
||||||
"rotation.\n"
|
|
||||||
"If a leading '@' is passed (@90) for display capture, then "
|
|
||||||
"the rotation is locked, and is relative to the natural device "
|
|
||||||
"orientation.\n"
|
|
||||||
"If '@' is passed alone, then the rotation is locked to the "
|
|
||||||
"initial device orientation.\n"
|
|
||||||
"Default is 0.",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Not really deprecated (--codec has never been released), but without
|
// Not really deprecated (--codec has never been released), but without
|
||||||
@ -330,7 +300,8 @@ static const struct sc_option options[] = {
|
|||||||
.argdesc = "width:height:x:y",
|
.argdesc = "width:height:x:y",
|
||||||
.text = "Crop the device screen on the server.\n"
|
.text = "Crop the device screen on the server.\n"
|
||||||
"The values are expressed in the device natural orientation "
|
"The values are expressed in the device natural orientation "
|
||||||
"(typically, portrait for a phone, landscape for a tablet).",
|
"(typically, portrait for a phone, landscape for a tablet). "
|
||||||
|
"Any --max-size value is computed on the cropped size.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.shortopt = 'd',
|
.shortopt = 'd',
|
||||||
@ -350,10 +321,12 @@ static const struct sc_option options[] = {
|
|||||||
.argdesc = "id",
|
.argdesc = "id",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// deprecated
|
|
||||||
.longopt_id = OPT_DISPLAY_BUFFER,
|
.longopt_id = OPT_DISPLAY_BUFFER,
|
||||||
.longopt = "display-buffer",
|
.longopt = "display-buffer",
|
||||||
.argdesc = "ms",
|
.argdesc = "ms",
|
||||||
|
.text = "Add a buffering delay (in milliseconds) before displaying. "
|
||||||
|
"This increases latency to compensate for jitter.\n"
|
||||||
|
"Default is 0 (no buffering).",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_DISPLAY_ID,
|
.longopt_id = OPT_DISPLAY_ID,
|
||||||
@ -498,10 +471,18 @@ static const struct sc_option options[] = {
|
|||||||
.text = "List video and audio encoders available on the device.",
|
.text = "List video and audio encoders available on the device.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// deprecated
|
|
||||||
.longopt_id = OPT_LOCK_VIDEO_ORIENTATION,
|
.longopt_id = OPT_LOCK_VIDEO_ORIENTATION,
|
||||||
.longopt = "lock-video-orientation",
|
.longopt = "lock-video-orientation",
|
||||||
.argdesc = "value",
|
.argdesc = "value",
|
||||||
|
.optional_arg = true,
|
||||||
|
.text = "Lock capture video orientation to value.\n"
|
||||||
|
"Possible values are \"unlocked\", \"initial\" (locked to the "
|
||||||
|
"initial orientation), 0, 90, 180 and 270. The values "
|
||||||
|
"represent the clockwise rotation from the natural device "
|
||||||
|
"orientation, in degrees.\n"
|
||||||
|
"Default is \"unlocked\".\n"
|
||||||
|
"Passing the option without argument is equivalent to passing "
|
||||||
|
"\"initial\".",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.shortopt = 'm',
|
.shortopt = 'm',
|
||||||
@ -591,11 +572,12 @@ static const struct sc_option options[] = {
|
|||||||
.optional_arg = true,
|
.optional_arg = true,
|
||||||
.text = "Create a new display with the specified resolution and "
|
.text = "Create a new display with the specified resolution and "
|
||||||
"density. If not provided, they default to the main display "
|
"density. If not provided, they default to the main display "
|
||||||
"dimensions and DPI.\n"
|
"dimensions and DPI, and --max-size is considered.\n"
|
||||||
"Examples:\n"
|
"Examples:\n"
|
||||||
" --new-display=1920x1080\n"
|
" --new-display=1920x1080\n"
|
||||||
" --new-display=1920x1080/420 # force 420 dpi\n"
|
" --new-display=1920x1080/420 # force 420 dpi\n"
|
||||||
" --new-display # main display size and density\n"
|
" --new-display # main display size and density\n"
|
||||||
|
" --new-display -m1920 # scaled to fit a max size of 1920\n"
|
||||||
" --new-display=/240 # main display size and 240 dpi",
|
" --new-display=/240 # main display size and 240 dpi",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -660,20 +642,6 @@ static const struct sc_option options[] = {
|
|||||||
.longopt = "no-power-on",
|
.longopt = "no-power-on",
|
||||||
.text = "Do not power on the device on start.",
|
.text = "Do not power on the device on start.",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.longopt_id = OPT_NO_VD_DESTROY_CONTENT,
|
|
||||||
.longopt = "no-vd-destroy-content",
|
|
||||||
.text = "Disable virtual display \"destroy content on removal\" "
|
|
||||||
"flag.\n"
|
|
||||||
"With this option, when the virtual display is closed, the "
|
|
||||||
"running apps are moved to the main display rather than being "
|
|
||||||
"destroyed.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.longopt_id = OPT_NO_VD_SYSTEM_DECORATIONS,
|
|
||||||
.longopt = "no-vd-system-decorations",
|
|
||||||
.text = "Disable virtual display system decorations flag.",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_NO_VIDEO,
|
.longopt_id = OPT_NO_VIDEO,
|
||||||
.longopt = "no-video",
|
.longopt = "no-video",
|
||||||
@ -826,13 +794,6 @@ static const struct sc_option options[] = {
|
|||||||
.longopt = "turn-screen-off",
|
.longopt = "turn-screen-off",
|
||||||
.text = "Turn the device screen off immediately.",
|
.text = "Turn the device screen off immediately.",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.longopt_id = OPT_SCREEN_OFF_TIMEOUT,
|
|
||||||
.longopt = "screen-off-timeout",
|
|
||||||
.argdesc = "seconds",
|
|
||||||
.text = "Set the screen off timeout while scrcpy is running (restore "
|
|
||||||
"the initial value on exit).",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_SHORTCUT_MOD,
|
.longopt_id = OPT_SHORTCUT_MOD,
|
||||||
.longopt = "shortcut-mod",
|
.longopt = "shortcut-mod",
|
||||||
@ -870,17 +831,16 @@ 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 connect the device over TCP/IP.\n"
|
.text = "Configure and reconnect 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.\n"
|
"this address before starting.",
|
||||||
"Prefix the address with a '+' to force a reconnection.",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_TIME_LIMIT,
|
.longopt_id = OPT_TIME_LIMIT,
|
||||||
@ -928,6 +888,8 @@ static const struct sc_option options[] = {
|
|||||||
.longopt = "v4l2-sink",
|
.longopt = "v4l2-sink",
|
||||||
.argdesc = "/dev/videoN",
|
.argdesc = "/dev/videoN",
|
||||||
.text = "Output to v4l2loopback device.\n"
|
.text = "Output to v4l2loopback device.\n"
|
||||||
|
"It requires to lock the video orientation (see "
|
||||||
|
"--lock-video-orientation).\n"
|
||||||
"This feature is only available on Linux.",
|
"This feature is only available on Linux.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -936,20 +898,11 @@ static const struct sc_option options[] = {
|
|||||||
.argdesc = "ms",
|
.argdesc = "ms",
|
||||||
.text = "Add a buffering delay (in milliseconds) before pushing "
|
.text = "Add a buffering delay (in milliseconds) before pushing "
|
||||||
"frames. This increases latency to compensate for jitter.\n"
|
"frames. This increases latency to compensate for jitter.\n"
|
||||||
"This option is similar to --video-buffer, but specific to "
|
"This option is similar to --display-buffer, but specific to "
|
||||||
"V4L2 sink.\n"
|
"V4L2 sink.\n"
|
||||||
"Default is 0 (no buffering).\n"
|
"Default is 0 (no buffering).\n"
|
||||||
"This option is only available on Linux.",
|
"This option is only available on Linux.",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.longopt_id = OPT_VIDEO_BUFFER,
|
|
||||||
.longopt = "video-buffer",
|
|
||||||
.argdesc = "ms",
|
|
||||||
.text = "Add a buffering delay (in milliseconds) before displaying "
|
|
||||||
"video frames.\n"
|
|
||||||
"This increases latency to compensate for jitter.\n"
|
|
||||||
"Default is 0 (no buffering).",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.longopt_id = OPT_VIDEO_CODEC,
|
.longopt_id = OPT_VIDEO_CODEC,
|
||||||
.longopt = "video-codec",
|
.longopt = "video-codec",
|
||||||
@ -1061,10 +1014,6 @@ static const struct sc_shortcut shortcuts[] = {
|
|||||||
.shortcuts = { "MOD+Shift+z" },
|
.shortcuts = { "MOD+Shift+z" },
|
||||||
.text = "Unpause display",
|
.text = "Unpause display",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.shortcuts = { "MOD+Shift+r" },
|
|
||||||
.text = "Reset video capture/encoding",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.shortcuts = { "MOD+g" },
|
.shortcuts = { "MOD+g" },
|
||||||
.text = "Resize window to 1:1 (pixel-perfect)",
|
.text = "Resize window to 1:1 (pixel-perfect)",
|
||||||
@ -1613,6 +1562,78 @@ parse_audio_output_buffer(const char *s, sc_tick *tick) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_lock_video_orientation(const char *s,
|
||||||
|
enum sc_lock_video_orientation *lock_mode) {
|
||||||
|
if (!s || !strcmp(s, "initial")) {
|
||||||
|
// Without argument, lock the initial orientation
|
||||||
|
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(s, "unlocked")) {
|
||||||
|
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(s, "0")) {
|
||||||
|
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(s, "90")) {
|
||||||
|
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_90;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(s, "180")) {
|
||||||
|
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_180;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(s, "270")) {
|
||||||
|
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_270;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(s, "1")) {
|
||||||
|
LOGW("--lock-video-orientation=1 is deprecated, use "
|
||||||
|
"--lock-video-orientation=270 instead.");
|
||||||
|
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_270;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(s, "2")) {
|
||||||
|
LOGW("--lock-video-orientation=2 is deprecated, use "
|
||||||
|
"--lock-video-orientation=180 instead.");
|
||||||
|
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_180;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(s, "3")) {
|
||||||
|
LOGW("--lock-video-orientation=3 is deprecated, use "
|
||||||
|
"--lock-video-orientation=90 instead.");
|
||||||
|
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_90;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGE("Unsupported --lock-video-orientation value: %s (expected initial, "
|
||||||
|
"unlocked, 0, 90, 180 or 270).", s);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_rotation(const char *s, uint8_t *rotation) {
|
||||||
|
long value;
|
||||||
|
bool ok = parse_integer_arg(s, &value, false, 0, 3, "rotation");
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*rotation = (uint8_t) value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_orientation(const char *s, enum sc_orientation *orientation) {
|
parse_orientation(const char *s, enum sc_orientation *orientation) {
|
||||||
if (!strcmp(s, "0")) {
|
if (!strcmp(s, "0")) {
|
||||||
@ -1652,32 +1673,6 @@ parse_orientation(const char *s, enum sc_orientation *orientation) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
parse_capture_orientation(const char *s, enum sc_orientation *orientation,
|
|
||||||
enum sc_orientation_lock *lock) {
|
|
||||||
if (*s == '\0') {
|
|
||||||
LOGE("Capture orientation may not be empty (expected 0, 90, 180, 270, "
|
|
||||||
"flip0, flip90, flip180 or flip270, possibly prefixed by '@')");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock the orientation by a leading '@'
|
|
||||||
if (s[0] == '@') {
|
|
||||||
// Consume '@'
|
|
||||||
++s;
|
|
||||||
if (*s == '\0') {
|
|
||||||
// Only '@': lock to the initial orientation (orientation is unused)
|
|
||||||
*lock = SC_ORIENTATION_LOCKED_INITIAL;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
*lock = SC_ORIENTATION_LOCKED_VALUE;
|
|
||||||
} else {
|
|
||||||
*lock = SC_ORIENTATION_UNLOCKED;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parse_orientation(s, orientation);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_window_position(const char *s, int16_t *position) {
|
parse_window_position(const char *s, int16_t *position) {
|
||||||
// special value for "auto"
|
// special value for "auto"
|
||||||
@ -2148,20 +2143,6 @@ parse_time_limit(const char *s, sc_tick *tick) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
parse_screen_off_timeout(const char *s, sc_tick *tick) {
|
|
||||||
long value;
|
|
||||||
// value in seconds, but must fit in 31 bits in milliseconds
|
|
||||||
bool ok = parse_integer_arg(s, &value, false, 0, 0x7FFFFFFF / 1000,
|
|
||||||
"screen off timeout");
|
|
||||||
if (!ok) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
*tick = SC_TICK_FROM_SEC(value);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_pause_on_exit(const char *s, enum sc_pause_on_exit *pause_on_exit) {
|
parse_pause_on_exit(const char *s, enum sc_pause_on_exit *pause_on_exit) {
|
||||||
if (!s || !strcmp(s, "true")) {
|
if (!s || !strcmp(s, "true")) {
|
||||||
@ -2287,8 +2268,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
opts->crop = optarg;
|
opts->crop = optarg;
|
||||||
break;
|
break;
|
||||||
case OPT_DISPLAY:
|
case OPT_DISPLAY:
|
||||||
LOGE("--display has been removed, use --display-id instead.");
|
LOGW("--display is deprecated, use --display-id instead.");
|
||||||
return false;
|
// fall through
|
||||||
case OPT_DISPLAY_ID:
|
case OPT_DISPLAY_ID:
|
||||||
if (!parse_display_id(optarg, &opts->display_id)) {
|
if (!parse_display_id(optarg, &opts->display_id)) {
|
||||||
return false;
|
return false;
|
||||||
@ -2352,13 +2333,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
"--mouse=uhid instead.");
|
"--mouse=uhid instead.");
|
||||||
return false;
|
return false;
|
||||||
case OPT_LOCK_VIDEO_ORIENTATION:
|
case OPT_LOCK_VIDEO_ORIENTATION:
|
||||||
LOGE("--lock-video-orientation has been removed, use "
|
if (!parse_lock_video_orientation(optarg,
|
||||||
"--capture-orientation instead.");
|
&opts->lock_video_orientation)) {
|
||||||
return false;
|
|
||||||
case OPT_CAPTURE_ORIENTATION:
|
|
||||||
if (!parse_capture_orientation(optarg,
|
|
||||||
&opts->capture_orientation,
|
|
||||||
&opts->capture_orientation_lock)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -2376,9 +2352,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
opts->control = false;
|
opts->control = false;
|
||||||
break;
|
break;
|
||||||
case OPT_NO_DISPLAY:
|
case OPT_NO_DISPLAY:
|
||||||
LOGE("--no-display has been removed, use --no-playback "
|
LOGW("--no-display is deprecated, use --no-playback instead.");
|
||||||
"instead.");
|
// fall through
|
||||||
return false;
|
|
||||||
case 'N':
|
case 'N':
|
||||||
opts->video_playback = false;
|
opts->video_playback = false;
|
||||||
opts->audio_playback = false;
|
opts->audio_playback = false;
|
||||||
@ -2464,9 +2439,32 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
opts->key_inject_mode = SC_KEY_INJECT_MODE_RAW;
|
opts->key_inject_mode = SC_KEY_INJECT_MODE_RAW;
|
||||||
break;
|
break;
|
||||||
case OPT_ROTATION:
|
case OPT_ROTATION:
|
||||||
LOGE("--rotation has been removed, use --orientation or "
|
LOGW("--rotation is deprecated, use --display-orientation "
|
||||||
"--capture-orientation instead.");
|
"instead.");
|
||||||
|
uint8_t rotation;
|
||||||
|
if (!parse_rotation(optarg, &rotation)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
assert(rotation <= 3);
|
||||||
|
switch (rotation) {
|
||||||
|
case 0:
|
||||||
|
opts->display_orientation = SC_ORIENTATION_0;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
// rotation 1 was 90° counterclockwise, but orientation
|
||||||
|
// is expressed clockwise
|
||||||
|
opts->display_orientation = SC_ORIENTATION_270;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
opts->display_orientation = SC_ORIENTATION_180;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
// rotation 3 was 270° counterclockwise, but orientation
|
||||||
|
// is expressed clockwise
|
||||||
|
opts->display_orientation = SC_ORIENTATION_90;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case OPT_DISPLAY_ORIENTATION:
|
case OPT_DISPLAY_ORIENTATION:
|
||||||
if (!parse_orientation(optarg, &opts->display_orientation)) {
|
if (!parse_orientation(optarg, &opts->display_orientation)) {
|
||||||
return false;
|
return false;
|
||||||
@ -2527,9 +2525,23 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case OPT_FORWARD_ALL_CLICKS:
|
case OPT_FORWARD_ALL_CLICKS:
|
||||||
LOGE("--forward-all-clicks has been removed, "
|
LOGW("--forward-all-clicks is deprecated, "
|
||||||
"use --mouse-bind=++++ instead.");
|
"use --mouse-bind=++++ instead.");
|
||||||
return false;
|
opts->mouse_bindings = (struct sc_mouse_bindings) {
|
||||||
|
.pri = {
|
||||||
|
.right_click = SC_MOUSE_BINDING_CLICK,
|
||||||
|
.middle_click = SC_MOUSE_BINDING_CLICK,
|
||||||
|
.click4 = SC_MOUSE_BINDING_CLICK,
|
||||||
|
.click5 = SC_MOUSE_BINDING_CLICK,
|
||||||
|
},
|
||||||
|
.sec = {
|
||||||
|
.right_click = SC_MOUSE_BINDING_CLICK,
|
||||||
|
.middle_click = SC_MOUSE_BINDING_CLICK,
|
||||||
|
.click4 = SC_MOUSE_BINDING_CLICK,
|
||||||
|
.click5 = SC_MOUSE_BINDING_CLICK,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
break;
|
||||||
case OPT_LEGACY_PASTE:
|
case OPT_LEGACY_PASTE:
|
||||||
opts->legacy_paste = true;
|
opts->legacy_paste = true;
|
||||||
break;
|
break;
|
||||||
@ -2537,11 +2549,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
opts->power_off_on_close = true;
|
opts->power_off_on_close = true;
|
||||||
break;
|
break;
|
||||||
case OPT_DISPLAY_BUFFER:
|
case OPT_DISPLAY_BUFFER:
|
||||||
LOGE("--display-buffer has been removed, use --video-buffer "
|
if (!parse_buffering_time(optarg, &opts->display_buffer)) {
|
||||||
"instead.");
|
|
||||||
return false;
|
|
||||||
case OPT_VIDEO_BUFFER:
|
|
||||||
if (!parse_buffering_time(optarg, &opts->video_buffer)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -2706,21 +2714,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
case OPT_START_APP:
|
case OPT_START_APP:
|
||||||
opts->start_app = optarg;
|
opts->start_app = optarg;
|
||||||
break;
|
break;
|
||||||
case OPT_SCREEN_OFF_TIMEOUT:
|
|
||||||
if (!parse_screen_off_timeout(optarg,
|
|
||||||
&opts->screen_off_timeout)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case OPT_ANGLE:
|
|
||||||
opts->angle = optarg;
|
|
||||||
break;
|
|
||||||
case OPT_NO_VD_DESTROY_CONTENT:
|
|
||||||
opts->vd_destroy_content = false;
|
|
||||||
break;
|
|
||||||
case OPT_NO_VD_SYSTEM_DECORATIONS:
|
|
||||||
opts->vd_system_decorations = false;
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
@ -2815,6 +2808,13 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts->lock_video_orientation ==
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
||||||
|
LOGI("Video orientation is locked for v4l2 sink. "
|
||||||
|
"See --lock-video-orientation.");
|
||||||
|
opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
|
||||||
|
}
|
||||||
|
|
||||||
// V4L2 could not handle size change.
|
// V4L2 could not handle size change.
|
||||||
// Do not log because downsizing on error is the default behavior,
|
// Do not log because downsizing on error is the default behavior,
|
||||||
// not an explicit request from the user.
|
// not an explicit request from the user.
|
||||||
@ -2822,7 +2822,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (opts->v4l2_buffer && !opts->v4l2_device) {
|
if (opts->v4l2_buffer && !opts->v4l2_device) {
|
||||||
LOGE("V4L2 buffer value without V4L2 sink");
|
LOGE("V4L2 buffer value without V4L2 sink\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -2841,8 +2841,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
if (otg) {
|
if (otg) {
|
||||||
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA;
|
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA;
|
||||||
} else if (!opts->video_playback) {
|
} else if (!opts->video_playback) {
|
||||||
LOGI("No video mirroring, SDK mouse disabled");
|
LOGI("No video mirroring, mouse mode switched to UHID");
|
||||||
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_DISABLED;
|
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_UHID;
|
||||||
} else {
|
} else {
|
||||||
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_SDK;
|
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_SDK;
|
||||||
}
|
}
|
||||||
@ -2904,6 +2904,13 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
LOGE("--new-display is incompatible with --no-video");
|
LOGE("--new-display is incompatible with --no-video");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts->max_size && opts->new_display[0] != '\0'
|
||||||
|
&& opts->new_display[0] != '/') {
|
||||||
|
// An explicit size is defined (not "" nor "/<dpi>")
|
||||||
|
LOGE("Cannot specify both --new-display size and -m/--max-size");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (otg) {
|
if (otg) {
|
||||||
|
@ -22,6 +22,9 @@
|
|||||||
#define MOTIONEVENT_ACTION_LABEL(value) \
|
#define MOTIONEVENT_ACTION_LABEL(value) \
|
||||||
ENUM_TO_LABEL(android_motionevent_action_labels, value)
|
ENUM_TO_LABEL(android_motionevent_action_labels, value)
|
||||||
|
|
||||||
|
#define SCREEN_POWER_MODE_LABEL(value) \
|
||||||
|
ENUM_TO_LABEL(screen_power_mode_labels, value)
|
||||||
|
|
||||||
static const char *const android_keyevent_action_labels[] = {
|
static const char *const android_keyevent_action_labels[] = {
|
||||||
"down",
|
"down",
|
||||||
"up",
|
"up",
|
||||||
@ -44,6 +47,14 @@ static const char *const android_motionevent_action_labels[] = {
|
|||||||
"btn-release",
|
"btn-release",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const char *const screen_power_mode_labels[] = {
|
||||||
|
"off",
|
||||||
|
"doze",
|
||||||
|
"normal",
|
||||||
|
"doze-suspend",
|
||||||
|
"suspend",
|
||||||
|
};
|
||||||
|
|
||||||
static const char *const copy_key_labels[] = {
|
static const char *const copy_key_labels[] = {
|
||||||
"none",
|
"none",
|
||||||
"copy",
|
"copy",
|
||||||
@ -147,15 +158,13 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
|||||||
size_t len = write_string(&buf[10], msg->set_clipboard.text,
|
size_t len = write_string(&buf[10], msg->set_clipboard.text,
|
||||||
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
|
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
|
||||||
return 10 + len;
|
return 10 + len;
|
||||||
case SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER:
|
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||||
buf[1] = msg->set_display_power.on;
|
buf[1] = msg->set_screen_power_mode.mode;
|
||||||
return 2;
|
return 2;
|
||||||
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
|
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
|
||||||
sc_write16be(&buf[1], msg->uhid_create.id);
|
sc_write16be(&buf[1], msg->uhid_create.id);
|
||||||
sc_write16be(&buf[3], msg->uhid_create.vendor_id);
|
|
||||||
sc_write16be(&buf[5], msg->uhid_create.product_id);
|
|
||||||
|
|
||||||
size_t index = 7;
|
size_t index = 3;
|
||||||
index += write_string_tiny(&buf[index], msg->uhid_create.name, 127);
|
index += write_string_tiny(&buf[index], msg->uhid_create.name, 127);
|
||||||
|
|
||||||
sc_write16be(&buf[index], msg->uhid_create.report_desc_size);
|
sc_write16be(&buf[index], msg->uhid_create.report_desc_size);
|
||||||
@ -183,7 +192,6 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
|||||||
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||||
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||||
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||||
case SC_CONTROL_MSG_TYPE_RESET_VIDEO:
|
|
||||||
// no additional data
|
// no additional data
|
||||||
return 1;
|
return 1;
|
||||||
default:
|
default:
|
||||||
@ -260,9 +268,9 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
|||||||
msg->set_clipboard.paste ? "paste" : "nopaste",
|
msg->set_clipboard.paste ? "paste" : "nopaste",
|
||||||
msg->set_clipboard.text);
|
msg->set_clipboard.text);
|
||||||
break;
|
break;
|
||||||
case SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER:
|
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||||
LOG_CMSG("display power %s",
|
LOG_CMSG("power mode %s",
|
||||||
msg->set_display_power.on ? "on" : "off");
|
SCREEN_POWER_MODE_LABEL(msg->set_screen_power_mode.mode));
|
||||||
break;
|
break;
|
||||||
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
LOG_CMSG("expand notification panel");
|
LOG_CMSG("expand notification panel");
|
||||||
@ -280,13 +288,9 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
|||||||
// Quote only if name is not null
|
// Quote only if name is not null
|
||||||
const char *name = msg->uhid_create.name;
|
const char *name = msg->uhid_create.name;
|
||||||
const char *quote = name ? "\"" : "";
|
const char *quote = name ? "\"" : "";
|
||||||
LOG_CMSG("UHID create [%" PRIu16 "] %04" PRIx16 ":%04" PRIx16
|
LOG_CMSG("UHID create [%" PRIu16 "] name=%s%s%s "
|
||||||
" name=%s%s%s report_desc_size=%" PRIu16,
|
"report_desc_size=%" PRIu16, msg->uhid_create.id,
|
||||||
msg->uhid_create.id,
|
quote, name, quote, msg->uhid_create.report_desc_size);
|
||||||
msg->uhid_create.vendor_id,
|
|
||||||
msg->uhid_create.product_id,
|
|
||||||
quote, name, quote,
|
|
||||||
msg->uhid_create.report_desc_size);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SC_CONTROL_MSG_TYPE_UHID_INPUT: {
|
case SC_CONTROL_MSG_TYPE_UHID_INPUT: {
|
||||||
@ -311,9 +315,6 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
|||||||
case SC_CONTROL_MSG_TYPE_START_APP:
|
case SC_CONTROL_MSG_TYPE_START_APP:
|
||||||
LOG_CMSG("start app \"%s\"", msg->start_app.name);
|
LOG_CMSG("start app \"%s\"", msg->start_app.name);
|
||||||
break;
|
break;
|
||||||
case SC_CONTROL_MSG_TYPE_RESET_VIDEO:
|
|
||||||
LOG_CMSG("reset video");
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
|
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
|
||||||
break;
|
break;
|
||||||
|
@ -35,14 +35,19 @@ enum sc_control_msg_type {
|
|||||||
SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS,
|
SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS,
|
||||||
SC_CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
SC_CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||||
SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||||
SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER,
|
SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
||||||
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
||||||
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||||
SC_CONTROL_MSG_TYPE_UHID_INPUT,
|
SC_CONTROL_MSG_TYPE_UHID_INPUT,
|
||||||
SC_CONTROL_MSG_TYPE_UHID_DESTROY,
|
SC_CONTROL_MSG_TYPE_UHID_DESTROY,
|
||||||
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
||||||
SC_CONTROL_MSG_TYPE_START_APP,
|
SC_CONTROL_MSG_TYPE_START_APP,
|
||||||
SC_CONTROL_MSG_TYPE_RESET_VIDEO,
|
};
|
||||||
|
|
||||||
|
enum sc_screen_power_mode {
|
||||||
|
// see <https://android.googlesource.com/platform/frameworks/base.git/+/pie-release-2/core/java/android/view/SurfaceControl.java#305>
|
||||||
|
SC_SCREEN_POWER_MODE_OFF = 0,
|
||||||
|
SC_SCREEN_POWER_MODE_NORMAL = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum sc_copy_key {
|
enum sc_copy_key {
|
||||||
@ -90,12 +95,10 @@ struct sc_control_msg {
|
|||||||
bool paste;
|
bool paste;
|
||||||
} set_clipboard;
|
} set_clipboard;
|
||||||
struct {
|
struct {
|
||||||
bool on;
|
enum sc_screen_power_mode mode;
|
||||||
} set_display_power;
|
} set_screen_power_mode;
|
||||||
struct {
|
struct {
|
||||||
uint16_t id;
|
uint16_t id;
|
||||||
uint16_t vendor_id;
|
|
||||||
uint16_t product_id;
|
|
||||||
const char *name; // pointer to static data
|
const char *name; // pointer to static data
|
||||||
uint16_t report_desc_size;
|
uint16_t report_desc_size;
|
||||||
const uint8_t *report_desc; // pointer to static data
|
const uint8_t *report_desc; // pointer to static data
|
||||||
|
@ -15,6 +15,7 @@ struct sc_hid_input {
|
|||||||
|
|
||||||
struct sc_hid_open {
|
struct sc_hid_open {
|
||||||
uint16_t hid_id;
|
uint16_t hid_id;
|
||||||
|
const char *name; // pointer to static memory
|
||||||
const uint8_t *report_desc; // pointer to static memory
|
const uint8_t *report_desc; // pointer to static memory
|
||||||
size_t report_desc_size;
|
size_t report_desc_size;
|
||||||
};
|
};
|
||||||
|
@ -52,10 +52,10 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
|
|||||||
0x09, 0x30,
|
0x09, 0x30,
|
||||||
// Usage (Y) Left stick y
|
// Usage (Y) Left stick y
|
||||||
0x09, 0x31,
|
0x09, 0x31,
|
||||||
// Usage (Rx) Right stick x
|
// Usage (Z) Right stick x
|
||||||
0x09, 0x33,
|
0x09, 0x32,
|
||||||
// Usage (Ry) Right stick y
|
// Usage (Rz) Right stick y
|
||||||
0x09, 0x34,
|
0x09, 0x35,
|
||||||
// Logical Minimum (0)
|
// Logical Minimum (0)
|
||||||
0x15, 0x00,
|
0x15, 0x00,
|
||||||
// Logical Maximum (65535)
|
// Logical Maximum (65535)
|
||||||
@ -65,15 +65,15 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
|
|||||||
0x75, 0x10,
|
0x75, 0x10,
|
||||||
// Report Count (4)
|
// Report Count (4)
|
||||||
0x95, 0x04,
|
0x95, 0x04,
|
||||||
// Input (Data, Variable, Absolute): 4x2 bytes (X, Y, Z, Rz)
|
// Input (Data, Variable, Absolute): 4 bytes (X, Y, Z, Rz)
|
||||||
0x81, 0x02,
|
0x81, 0x02,
|
||||||
|
|
||||||
// Usage Page (Generic Desktop)
|
// Usage Page (Simulation Controls)
|
||||||
0x05, 0x01,
|
0x05, 0x02,
|
||||||
// Usage (Z)
|
// Usage (Brake)
|
||||||
0x09, 0x32,
|
0x09, 0xC5,
|
||||||
// Usage (Rz)
|
// Usage (Accelerator)
|
||||||
0x09, 0x35,
|
0x09, 0xC4,
|
||||||
// Logical Minimum (0)
|
// Logical Minimum (0)
|
||||||
0x15, 0x00,
|
0x15, 0x00,
|
||||||
// Logical Maximum (32767)
|
// Logical Maximum (32767)
|
||||||
@ -82,7 +82,7 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
|
|||||||
0x75, 0x10,
|
0x75, 0x10,
|
||||||
// Report Count (2)
|
// Report Count (2)
|
||||||
0x95, 0x02,
|
0x95, 0x02,
|
||||||
// Input (Data, Variable, Absolute): 2x2 bytes (L2, R2)
|
// Input (Data, Variable, Absolute): 2 bytes (L2, R2)
|
||||||
0x81, 0x02,
|
0x81, 0x02,
|
||||||
|
|
||||||
// Usage Page (Buttons)
|
// Usage Page (Buttons)
|
||||||
@ -182,7 +182,7 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
|
|||||||
* `------------- SC_GAMEPAD_BUTTON_RIGHT_STICK
|
* `------------- SC_GAMEPAD_BUTTON_RIGHT_STICK
|
||||||
*
|
*
|
||||||
* +---------------+
|
* +---------------+
|
||||||
* byte 14: |0 0 0 0 . . . .| hat switch (dpad) position (0-8)
|
* byte 14: |0 0 0 . . . . .| hat switch (dpad) position (0-8)
|
||||||
* +---------------+
|
* +---------------+
|
||||||
* 9 possible positions and their values:
|
* 9 possible positions and their values:
|
||||||
* 8 1 2
|
* 8 1 2
|
||||||
@ -191,19 +191,16 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
|
|||||||
* (8 is top-left, 1 is top, 2 is top-right, etc.)
|
* (8 is top-left, 1 is top, 2 is top-right, etc.)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// [-32768 to 32767] -> [0 to 65535]
|
|
||||||
#define AXIS_RESCALE(V) (uint16_t) (((int32_t) V) + 0x8000)
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_hid_gamepad_slot_init(struct sc_hid_gamepad_slot *slot,
|
sc_hid_gamepad_slot_init(struct sc_hid_gamepad_slot *slot,
|
||||||
uint32_t gamepad_id) {
|
uint32_t gamepad_id) {
|
||||||
assert(gamepad_id != SC_GAMEPAD_ID_INVALID);
|
assert(gamepad_id != SC_GAMEPAD_ID_INVALID);
|
||||||
slot->gamepad_id = gamepad_id;
|
slot->gamepad_id = gamepad_id;
|
||||||
slot->buttons = 0;
|
slot->buttons = 0;
|
||||||
slot->axis_left_x = AXIS_RESCALE(0);
|
slot->axis_left_x = 0;
|
||||||
slot->axis_left_y = AXIS_RESCALE(0);
|
slot->axis_left_y = 0;
|
||||||
slot->axis_right_x = AXIS_RESCALE(0);
|
slot->axis_right_x = 0;
|
||||||
slot->axis_right_y = AXIS_RESCALE(0);
|
slot->axis_right_y = 0;
|
||||||
slot->axis_left_trigger = 0;
|
slot->axis_left_trigger = 0;
|
||||||
slot->axis_right_trigger = 0;
|
slot->axis_right_trigger = 0;
|
||||||
}
|
}
|
||||||
@ -246,8 +243,14 @@ sc_hid_gamepad_generate_open(struct sc_hid_gamepad *hid,
|
|||||||
|
|
||||||
sc_hid_gamepad_slot_init(&hid->slots[slot_idx], gamepad_id);
|
sc_hid_gamepad_slot_init(&hid->slots[slot_idx], gamepad_id);
|
||||||
|
|
||||||
|
SDL_GameController* game_controller =
|
||||||
|
SDL_GameControllerFromInstanceID(gamepad_id);
|
||||||
|
assert(game_controller);
|
||||||
|
const char *name = SDL_GameControllerName(game_controller);
|
||||||
|
|
||||||
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
|
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
|
||||||
hid_open->hid_id = hid_id;
|
hid_open->hid_id = hid_id;
|
||||||
|
hid_open->name = name;
|
||||||
hid_open->report_desc = SC_HID_GAMEPAD_REPORT_DESC;
|
hid_open->report_desc = SC_HID_GAMEPAD_REPORT_DESC;
|
||||||
hid_open->report_desc_size = sizeof(SC_HID_GAMEPAD_REPORT_DESC);
|
hid_open->report_desc_size = sizeof(SC_HID_GAMEPAD_REPORT_DESC);
|
||||||
|
|
||||||
@ -420,6 +423,8 @@ sc_hid_gamepad_generate_input_from_axis(struct sc_hid_gamepad *hid,
|
|||||||
|
|
||||||
struct sc_hid_gamepad_slot *slot = &hid->slots[slot_idx];
|
struct sc_hid_gamepad_slot *slot = &hid->slots[slot_idx];
|
||||||
|
|
||||||
|
// [-32768 to 32767] -> [0 to 65535]
|
||||||
|
#define AXIS_RESCALE(V) (uint16_t) (((int32_t) V) + 0x8000)
|
||||||
switch (event->axis) {
|
switch (event->axis) {
|
||||||
case SC_GAMEPAD_AXIS_LEFTX:
|
case SC_GAMEPAD_AXIS_LEFTX:
|
||||||
slot->axis_left_x = AXIS_RESCALE(event->value);
|
slot->axis_left_x = AXIS_RESCALE(event->value);
|
||||||
|
@ -335,6 +335,7 @@ sc_hid_keyboard_generate_input_from_mods(struct sc_hid_input *hid_input,
|
|||||||
|
|
||||||
void sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open) {
|
void sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open) {
|
||||||
hid_open->hid_id = SC_HID_ID_KEYBOARD;
|
hid_open->hid_id = SC_HID_ID_KEYBOARD;
|
||||||
|
hid_open->name = NULL; // No name specified after "scrcpy"
|
||||||
hid_open->report_desc = SC_HID_KEYBOARD_REPORT_DESC;
|
hid_open->report_desc = SC_HID_KEYBOARD_REPORT_DESC;
|
||||||
hid_open->report_desc_size = sizeof(SC_HID_KEYBOARD_REPORT_DESC);
|
hid_open->report_desc_size = sizeof(SC_HID_KEYBOARD_REPORT_DESC);
|
||||||
}
|
}
|
||||||
|
@ -190,6 +190,7 @@ sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input,
|
|||||||
|
|
||||||
void sc_hid_mouse_generate_open(struct sc_hid_open *hid_open) {
|
void sc_hid_mouse_generate_open(struct sc_hid_open *hid_open) {
|
||||||
hid_open->hid_id = SC_HID_ID_MOUSE;
|
hid_open->hid_id = SC_HID_ID_MOUSE;
|
||||||
|
hid_open->name = NULL; // No name specified after "scrcpy"
|
||||||
hid_open->report_desc = SC_HID_MOUSE_REPORT_DESC;
|
hid_open->report_desc = SC_HID_MOUSE_REPORT_DESC;
|
||||||
hid_open->report_desc_size = sizeof(SC_HID_MOUSE_REPORT_DESC);
|
hid_open->report_desc_size = sizeof(SC_HID_MOUSE_REPORT_DESC);
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "util/env.h"
|
|
||||||
#include "util/file.h"
|
#include "util/file.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str.h"
|
#include "util/str.h"
|
||||||
@ -20,22 +19,35 @@
|
|||||||
|
|
||||||
static char *
|
static char *
|
||||||
get_icon_path(void) {
|
get_icon_path(void) {
|
||||||
char *icon_path = sc_get_env("SCRCPY_ICON_PATH");
|
#ifdef __WINDOWS__
|
||||||
if (icon_path) {
|
const wchar_t *icon_path_env = _wgetenv(L"SCRCPY_ICON_PATH");
|
||||||
|
#else
|
||||||
|
const char *icon_path_env = getenv("SCRCPY_ICON_PATH");
|
||||||
|
#endif
|
||||||
|
if (icon_path_env) {
|
||||||
// if the envvar is set, use it
|
// if the envvar is set, use it
|
||||||
|
#ifdef __WINDOWS__
|
||||||
|
char *icon_path = sc_str_from_wchars(icon_path_env);
|
||||||
|
#else
|
||||||
|
char *icon_path = strdup(icon_path_env);
|
||||||
|
#endif
|
||||||
|
if (!icon_path) {
|
||||||
|
LOG_OOM();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
LOGD("Using SCRCPY_ICON_PATH: %s", icon_path);
|
LOGD("Using SCRCPY_ICON_PATH: %s", icon_path);
|
||||||
return icon_path;
|
return icon_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef PORTABLE
|
#ifndef PORTABLE
|
||||||
LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH);
|
LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH);
|
||||||
icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH);
|
char *icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH);
|
||||||
if (!icon_path) {
|
if (!icon_path) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
icon_path = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME);
|
char *icon_path = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME);
|
||||||
if (!icon_path) {
|
if (!icon_path) {
|
||||||
LOGE("Could not get icon path");
|
LOGE("Could not get icon path");
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -412,12 +412,18 @@ struct sc_touch_event {
|
|||||||
float pressure;
|
float pressure;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum sc_gamepad_device_event_type {
|
||||||
|
SC_GAMEPAD_DEVICE_ADDED,
|
||||||
|
SC_GAMEPAD_DEVICE_REMOVED,
|
||||||
|
};
|
||||||
|
|
||||||
// As documented in <https://wiki.libsdl.org/SDL2/SDL_JoystickID>:
|
// As documented in <https://wiki.libsdl.org/SDL2/SDL_JoystickID>:
|
||||||
// The ID value starts at 0 and increments from there. The value -1 is an
|
// The ID value starts at 0 and increments from there. The value -1 is an
|
||||||
// invalid ID.
|
// invalid ID.
|
||||||
#define SC_GAMEPAD_ID_INVALID UINT32_C(-1)
|
#define SC_GAMEPAD_ID_INVALID UINT32_C(-1)
|
||||||
|
|
||||||
struct sc_gamepad_device_event {
|
struct sc_gamepad_device_event {
|
||||||
|
enum sc_gamepad_device_event_type type;
|
||||||
uint32_t gamepad_id;
|
uint32_t gamepad_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -497,6 +503,16 @@ sc_mouse_buttons_state_from_sdl(uint32_t buttons_state) {
|
|||||||
return buttons_state;
|
return buttons_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline enum sc_gamepad_device_event_type
|
||||||
|
sc_gamepad_device_event_type_from_sdl_type(uint32_t type) {
|
||||||
|
assert(type == SDL_CONTROLLERDEVICEADDED
|
||||||
|
|| type == SDL_CONTROLLERDEVICEREMOVED);
|
||||||
|
if (type == SDL_CONTROLLERDEVICEADDED) {
|
||||||
|
return SC_GAMEPAD_DEVICE_ADDED;
|
||||||
|
}
|
||||||
|
return SC_GAMEPAD_DEVICE_REMOVED;
|
||||||
|
}
|
||||||
|
|
||||||
static inline enum sc_gamepad_axis
|
static inline enum sc_gamepad_axis
|
||||||
sc_gamepad_axis_from_sdl(uint8_t axis) {
|
sc_gamepad_axis_from_sdl(uint8_t axis) {
|
||||||
if (axis <= SDL_CONTROLLER_AXIS_TRIGGERRIGHT) {
|
if (axis <= SDL_CONTROLLER_AXIS_TRIGGERRIGHT) {
|
||||||
|
@ -203,12 +203,13 @@ set_device_clipboard(struct sc_input_manager *im, bool paste,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
set_display_power(struct sc_input_manager *im, bool on) {
|
set_screen_power_mode(struct sc_input_manager *im,
|
||||||
|
enum sc_screen_power_mode mode) {
|
||||||
assert(im->controller);
|
assert(im->controller);
|
||||||
|
|
||||||
struct sc_control_msg msg;
|
struct sc_control_msg msg;
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER;
|
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
|
||||||
msg.set_display_power.on = on;
|
msg.set_screen_power_mode.mode = mode;
|
||||||
|
|
||||||
if (!sc_controller_push_msg(im->controller, &msg)) {
|
if (!sc_controller_push_msg(im->controller, &msg)) {
|
||||||
LOGW("Could not request 'set screen power mode'");
|
LOGW("Could not request 'set screen power mode'");
|
||||||
@ -284,18 +285,6 @@ open_hard_keyboard_settings(struct sc_input_manager *im) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
reset_video(struct sc_input_manager *im) {
|
|
||||||
assert(im->controller);
|
|
||||||
|
|
||||||
struct sc_control_msg msg;
|
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_RESET_VIDEO;
|
|
||||||
|
|
||||||
if (!sc_controller_push_msg(im->controller, &msg)) {
|
|
||||||
LOGW("Could not request reset video");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
apply_orientation_transform(struct sc_input_manager *im,
|
apply_orientation_transform(struct sc_input_manager *im,
|
||||||
enum sc_orientation transform) {
|
enum sc_orientation transform) {
|
||||||
@ -426,8 +415,10 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
return;
|
return;
|
||||||
case SDLK_o:
|
case SDLK_o:
|
||||||
if (control && !repeat && down && !paused) {
|
if (control && !repeat && down && !paused) {
|
||||||
bool on = shift;
|
enum sc_screen_power_mode mode = shift
|
||||||
set_display_power(im, on);
|
? SC_SCREEN_POWER_MODE_NORMAL
|
||||||
|
: SC_SCREEN_POWER_MODE_OFF;
|
||||||
|
set_screen_power_mode(im, mode);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_z:
|
case SDLK_z:
|
||||||
@ -533,13 +524,9 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_r:
|
case SDLK_r:
|
||||||
if (control && !repeat && down && !paused) {
|
if (control && !shift && !repeat && down && !paused) {
|
||||||
if (shift) {
|
|
||||||
reset_video(im);
|
|
||||||
} else {
|
|
||||||
rotate_device(im);
|
rotate_device(im);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
case SDLK_k:
|
case SDLK_k:
|
||||||
if (control && !shift && !repeat && down && !paused
|
if (control && !shift && !repeat && down && !paused
|
||||||
@ -908,6 +895,7 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
|
|||||||
static void
|
static void
|
||||||
sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
|
sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
|
||||||
const SDL_ControllerDeviceEvent *event) {
|
const SDL_ControllerDeviceEvent *event) {
|
||||||
|
SDL_JoystickID id;
|
||||||
if (event->type == SDL_CONTROLLERDEVICEADDED) {
|
if (event->type == SDL_CONTROLLERDEVICEADDED) {
|
||||||
SDL_GameController *gc = SDL_GameControllerOpen(event->which);
|
SDL_GameController *gc = SDL_GameControllerOpen(event->which);
|
||||||
if (!gc) {
|
if (!gc) {
|
||||||
@ -922,12 +910,9 @@ sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_gamepad_device_event evt = {
|
id = SDL_JoystickInstanceID(joystick);
|
||||||
.gamepad_id = SDL_JoystickInstanceID(joystick),
|
|
||||||
};
|
|
||||||
im->gp->ops->process_gamepad_added(im->gp, &evt);
|
|
||||||
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||||
SDL_JoystickID id = event->which;
|
id = event->which;
|
||||||
|
|
||||||
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
|
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
|
||||||
if (gc) {
|
if (gc) {
|
||||||
@ -935,15 +920,16 @@ sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
|
|||||||
} else {
|
} else {
|
||||||
LOGW("Unknown gamepad device removed");
|
LOGW("Unknown gamepad device removed");
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_gamepad_device_event evt = {
|
|
||||||
.gamepad_id = id,
|
|
||||||
};
|
|
||||||
im->gp->ops->process_gamepad_removed(im->gp, &evt);
|
|
||||||
} else {
|
} else {
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct sc_gamepad_device_event evt = {
|
||||||
|
.type = sc_gamepad_device_event_type_from_sdl_type(event->type),
|
||||||
|
.gamepad_id = id,
|
||||||
|
};
|
||||||
|
im->gp->ops->process_gamepad_device(im->gp, &evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -50,8 +50,7 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.video_bit_rate = 0,
|
.video_bit_rate = 0,
|
||||||
.audio_bit_rate = 0,
|
.audio_bit_rate = 0,
|
||||||
.max_fps = NULL,
|
.max_fps = NULL,
|
||||||
.capture_orientation = SC_ORIENTATION_0,
|
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
|
||||||
.capture_orientation_lock = SC_ORIENTATION_UNLOCKED,
|
|
||||||
.display_orientation = SC_ORIENTATION_0,
|
.display_orientation = SC_ORIENTATION_0,
|
||||||
.record_orientation = SC_ORIENTATION_0,
|
.record_orientation = SC_ORIENTATION_0,
|
||||||
.window_x = SC_WINDOW_POSITION_UNDEFINED,
|
.window_x = SC_WINDOW_POSITION_UNDEFINED,
|
||||||
@ -59,11 +58,10 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.window_width = 0,
|
.window_width = 0,
|
||||||
.window_height = 0,
|
.window_height = 0,
|
||||||
.display_id = 0,
|
.display_id = 0,
|
||||||
.video_buffer = 0,
|
.display_buffer = 0,
|
||||||
.audio_buffer = -1, // depends on the audio format,
|
.audio_buffer = -1, // depends on the audio format,
|
||||||
.audio_output_buffer = SC_TICK_FROM_MS(5),
|
.audio_output_buffer = SC_TICK_FROM_MS(5),
|
||||||
.time_limit = 0,
|
.time_limit = 0,
|
||||||
.screen_off_timeout = -1,
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
.v4l2_device = NULL,
|
.v4l2_device = NULL,
|
||||||
.v4l2_buffer = 0,
|
.v4l2_buffer = 0,
|
||||||
@ -107,9 +105,6 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||||||
.audio_dup = false,
|
.audio_dup = false,
|
||||||
.new_display = NULL,
|
.new_display = NULL,
|
||||||
.start_app = NULL,
|
.start_app = NULL,
|
||||||
.angle = NULL,
|
|
||||||
.vd_destroy_content = true,
|
|
||||||
.vd_system_decorations = true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum sc_orientation
|
enum sc_orientation
|
||||||
|
@ -84,12 +84,6 @@ enum sc_orientation { // v v v
|
|||||||
SC_ORIENTATION_FLIP_270, // 1 1 1
|
SC_ORIENTATION_FLIP_270, // 1 1 1
|
||||||
};
|
};
|
||||||
|
|
||||||
enum sc_orientation_lock {
|
|
||||||
SC_ORIENTATION_UNLOCKED,
|
|
||||||
SC_ORIENTATION_LOCKED_VALUE, // lock to specified orientation
|
|
||||||
SC_ORIENTATION_LOCKED_INITIAL, // lock to initial device orientation
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
sc_orientation_is_mirror(enum sc_orientation orientation) {
|
sc_orientation_is_mirror(enum sc_orientation orientation) {
|
||||||
assert(!(orientation & ~7));
|
assert(!(orientation & ~7));
|
||||||
@ -136,6 +130,16 @@ sc_orientation_get_name(enum sc_orientation orientation) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum sc_lock_video_orientation {
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
|
||||||
|
// lock the current orientation when scrcpy starts
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_90 = 3,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_180 = 2,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_270 = 1,
|
||||||
|
};
|
||||||
|
|
||||||
enum sc_keyboard_input_mode {
|
enum sc_keyboard_input_mode {
|
||||||
SC_KEYBOARD_INPUT_MODE_AUTO,
|
SC_KEYBOARD_INPUT_MODE_AUTO,
|
||||||
SC_KEYBOARD_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
|
SC_KEYBOARD_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
|
||||||
@ -247,9 +251,7 @@ struct scrcpy_options {
|
|||||||
uint32_t video_bit_rate;
|
uint32_t video_bit_rate;
|
||||||
uint32_t audio_bit_rate;
|
uint32_t audio_bit_rate;
|
||||||
const char *max_fps; // float to be parsed by the server
|
const char *max_fps; // float to be parsed by the server
|
||||||
const char *angle; // float to be parsed by the server
|
enum sc_lock_video_orientation lock_video_orientation;
|
||||||
enum sc_orientation capture_orientation;
|
|
||||||
enum sc_orientation_lock capture_orientation_lock;
|
|
||||||
enum sc_orientation display_orientation;
|
enum sc_orientation display_orientation;
|
||||||
enum sc_orientation record_orientation;
|
enum sc_orientation record_orientation;
|
||||||
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
||||||
@ -257,11 +259,10 @@ struct scrcpy_options {
|
|||||||
uint16_t window_width;
|
uint16_t window_width;
|
||||||
uint16_t window_height;
|
uint16_t window_height;
|
||||||
uint32_t display_id;
|
uint32_t display_id;
|
||||||
sc_tick video_buffer;
|
sc_tick display_buffer;
|
||||||
sc_tick audio_buffer;
|
sc_tick audio_buffer;
|
||||||
sc_tick audio_output_buffer;
|
sc_tick audio_output_buffer;
|
||||||
sc_tick time_limit;
|
sc_tick time_limit;
|
||||||
sc_tick screen_off_timeout;
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
const char *v4l2_device;
|
const char *v4l2_device;
|
||||||
sc_tick v4l2_buffer;
|
sc_tick v4l2_buffer;
|
||||||
@ -310,8 +311,6 @@ struct scrcpy_options {
|
|||||||
bool audio_dup;
|
bool audio_dup;
|
||||||
const char *new_display; // [<width>x<height>][/<dpi>] parsed by the server
|
const char *new_display; // [<width>x<height>][/<dpi>] parsed by the server
|
||||||
const char *start_app;
|
const char *start_app;
|
||||||
bool vd_destroy_content;
|
|
||||||
bool vd_system_decorations;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const struct scrcpy_options scrcpy_options_default;
|
extern const struct scrcpy_options scrcpy_options_default;
|
||||||
|
@ -143,14 +143,8 @@ sc_recorder_open_output_file(struct sc_recorder *recorder) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *file_url = sc_str_concat("file:", recorder->filename);
|
int ret = avio_open(&recorder->ctx->pb, recorder->filename,
|
||||||
if (!file_url) {
|
AVIO_FLAG_WRITE);
|
||||||
avformat_free_context(recorder->ctx);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ret = avio_open(&recorder->ctx->pb, file_url, AVIO_FLAG_WRITE);
|
|
||||||
free(file_url);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
LOGE("Failed to open output file: %s", recorder->filename);
|
LOGE("Failed to open output file: %s", recorder->filename);
|
||||||
avformat_free_context(recorder->ctx);
|
avformat_free_context(recorder->ctx);
|
||||||
|
@ -53,7 +53,7 @@ struct scrcpy {
|
|||||||
struct sc_decoder video_decoder;
|
struct sc_decoder video_decoder;
|
||||||
struct sc_decoder audio_decoder;
|
struct sc_decoder audio_decoder;
|
||||||
struct sc_recorder recorder;
|
struct sc_recorder recorder;
|
||||||
struct sc_delay_buffer video_buffer;
|
struct sc_delay_buffer display_buffer;
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
struct sc_v4l2_sink v4l2_sink;
|
struct sc_v4l2_sink v4l2_sink;
|
||||||
struct sc_delay_buffer v4l2_buffer;
|
struct sc_delay_buffer v4l2_buffer;
|
||||||
@ -428,10 +428,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.video_bit_rate = options->video_bit_rate,
|
.video_bit_rate = options->video_bit_rate,
|
||||||
.audio_bit_rate = options->audio_bit_rate,
|
.audio_bit_rate = options->audio_bit_rate,
|
||||||
.max_fps = options->max_fps,
|
.max_fps = options->max_fps,
|
||||||
.angle = options->angle,
|
.lock_video_orientation = options->lock_video_orientation,
|
||||||
.screen_off_timeout = options->screen_off_timeout,
|
|
||||||
.capture_orientation = options->capture_orientation,
|
|
||||||
.capture_orientation_lock = options->capture_orientation_lock,
|
|
||||||
.control = options->control,
|
.control = options->control,
|
||||||
.display_id = options->display_id,
|
.display_id = options->display_id,
|
||||||
.new_display = options->new_display,
|
.new_display = options->new_display,
|
||||||
@ -458,8 +455,6 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.power_on = options->power_on,
|
.power_on = options->power_on,
|
||||||
.kill_adb_on_close = options->kill_adb_on_close,
|
.kill_adb_on_close = options->kill_adb_on_close,
|
||||||
.camera_high_speed = options->camera_high_speed,
|
.camera_high_speed = options->camera_high_speed,
|
||||||
.vd_destroy_content = options->vd_destroy_content,
|
|
||||||
.vd_system_decorations = options->vd_system_decorations,
|
|
||||||
.list = options->list,
|
.list = options->list,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -820,11 +815,11 @@ aoa_complete:
|
|||||||
|
|
||||||
if (options->video_playback) {
|
if (options->video_playback) {
|
||||||
struct sc_frame_source *src = &s->video_decoder.frame_source;
|
struct sc_frame_source *src = &s->video_decoder.frame_source;
|
||||||
if (options->video_buffer) {
|
if (options->display_buffer) {
|
||||||
sc_delay_buffer_init(&s->video_buffer,
|
sc_delay_buffer_init(&s->display_buffer,
|
||||||
options->video_buffer, true);
|
options->display_buffer, true);
|
||||||
sc_frame_source_add_sink(src, &s->video_buffer.frame_sink);
|
sc_frame_source_add_sink(src, &s->display_buffer.frame_sink);
|
||||||
src = &s->video_buffer.frame_source;
|
src = &s->display_buffer.frame_source;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_frame_source_add_sink(src, &s->screen.frame_sink);
|
sc_frame_source_add_sink(src, &s->screen.frame_sink);
|
||||||
@ -878,11 +873,11 @@ aoa_complete:
|
|||||||
// everything is set up
|
// everything is set up
|
||||||
if (options->control && options->turn_screen_off) {
|
if (options->control && options->turn_screen_off) {
|
||||||
struct sc_control_msg msg;
|
struct sc_control_msg msg;
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER;
|
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
|
||||||
msg.set_display_power.on = false;
|
msg.set_screen_power_mode.mode = SC_SCREEN_POWER_MODE_OFF;
|
||||||
|
|
||||||
if (!sc_controller_push_msg(&s->controller, &msg)) {
|
if (!sc_controller_push_msg(&s->controller, &msg)) {
|
||||||
LOGW("Could not request 'set display power'");
|
LOGW("Could not request 'set screen power mode'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
125
app/src/server.c
125
app/src/server.c
@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
#include "adb/adb.h"
|
#include "adb/adb.h"
|
||||||
#include "util/binary.h"
|
#include "util/binary.h"
|
||||||
#include "util/env.h"
|
|
||||||
#include "util/file.h"
|
#include "util/file.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net_intr.h"
|
#include "util/net_intr.h"
|
||||||
@ -26,22 +25,35 @@
|
|||||||
|
|
||||||
static char *
|
static char *
|
||||||
get_server_path(void) {
|
get_server_path(void) {
|
||||||
char *server_path = sc_get_env("SCRCPY_SERVER_PATH");
|
#ifdef __WINDOWS__
|
||||||
if (server_path) {
|
const wchar_t *server_path_env = _wgetenv(L"SCRCPY_SERVER_PATH");
|
||||||
|
#else
|
||||||
|
const char *server_path_env = getenv("SCRCPY_SERVER_PATH");
|
||||||
|
#endif
|
||||||
|
if (server_path_env) {
|
||||||
// if the envvar is set, use it
|
// if the envvar is set, use it
|
||||||
|
#ifdef __WINDOWS__
|
||||||
|
char *server_path = sc_str_from_wchars(server_path_env);
|
||||||
|
#else
|
||||||
|
char *server_path = strdup(server_path_env);
|
||||||
|
#endif
|
||||||
|
if (!server_path) {
|
||||||
|
LOG_OOM();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
LOGD("Using SCRCPY_SERVER_PATH: %s", server_path);
|
LOGD("Using SCRCPY_SERVER_PATH: %s", server_path);
|
||||||
return server_path;
|
return server_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef PORTABLE
|
#ifndef PORTABLE
|
||||||
LOGD("Using server: " SC_SERVER_PATH_DEFAULT);
|
LOGD("Using server: " SC_SERVER_PATH_DEFAULT);
|
||||||
server_path = strdup(SC_SERVER_PATH_DEFAULT);
|
char *server_path = strdup(SC_SERVER_PATH_DEFAULT);
|
||||||
if (!server_path) {
|
if (!server_path) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
server_path = sc_file_get_local_path(SC_SERVER_FILENAME);
|
char *server_path = sc_file_get_local_path(SC_SERVER_FILENAME);
|
||||||
if (!server_path) {
|
if (!server_path) {
|
||||||
LOGE("Could not get local file path, "
|
LOGE("Could not get local file path, "
|
||||||
"using " SC_SERVER_FILENAME " from current directory");
|
"using " SC_SERVER_FILENAME " from current directory");
|
||||||
@ -189,31 +201,18 @@ execute_server(struct sc_server *server,
|
|||||||
cmd[count++] = "app_process";
|
cmd[count++] = "app_process";
|
||||||
|
|
||||||
#ifdef SERVER_DEBUGGER
|
#ifdef SERVER_DEBUGGER
|
||||||
uint16_t sdk_version = sc_adb_get_device_sdk_version(&server->intr, serial);
|
|
||||||
if (!sdk_version) {
|
|
||||||
LOGE("Could not determine SDK version");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
# define SERVER_DEBUGGER_PORT "5005"
|
# define SERVER_DEBUGGER_PORT "5005"
|
||||||
const char *dbg;
|
cmd[count++] =
|
||||||
if (sdk_version < 28) {
|
# ifdef SERVER_DEBUGGER_METHOD_NEW
|
||||||
// Android < 9
|
/* Android 9 and above */
|
||||||
dbg = "-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
|
"-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y,"
|
||||||
|
"server=y,address="
|
||||||
|
# else
|
||||||
|
/* Android 8 and below */
|
||||||
|
"-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
|
||||||
|
# endif
|
||||||
SERVER_DEBUGGER_PORT;
|
SERVER_DEBUGGER_PORT;
|
||||||
} else if (sdk_version < 30) {
|
|
||||||
// Android >= 9 && Android < 11
|
|
||||||
dbg = "-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,"
|
|
||||||
"suspend=y,server=y,address=" SERVER_DEBUGGER_PORT;
|
|
||||||
} else {
|
|
||||||
// Android >= 11
|
|
||||||
// Contrary to the other methods, this does not suspend on start.
|
|
||||||
// <https://github.com/Genymobile/scrcpy/pull/5466>
|
|
||||||
dbg = "-XjdwpProvider:adbconnection";
|
|
||||||
}
|
|
||||||
cmd[count++] = dbg;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
cmd[count++] = "/"; // unused
|
cmd[count++] = "/"; // unused
|
||||||
cmd[count++] = "com.genymobile.scrcpy.Server";
|
cmd[count++] = "com.genymobile.scrcpy.Server";
|
||||||
cmd[count++] = SCRCPY_VERSION;
|
cmd[count++] = SCRCPY_VERSION;
|
||||||
@ -275,21 +274,9 @@ execute_server(struct sc_server *server,
|
|||||||
VALIDATE_STRING(params->max_fps);
|
VALIDATE_STRING(params->max_fps);
|
||||||
ADD_PARAM("max_fps=%s", params->max_fps);
|
ADD_PARAM("max_fps=%s", params->max_fps);
|
||||||
}
|
}
|
||||||
if (params->angle) {
|
if (params->lock_video_orientation != SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
||||||
VALIDATE_STRING(params->angle);
|
ADD_PARAM("lock_video_orientation=%" PRIi8,
|
||||||
ADD_PARAM("angle=%s", params->angle);
|
params->lock_video_orientation);
|
||||||
}
|
|
||||||
if (params->capture_orientation_lock != SC_ORIENTATION_UNLOCKED
|
|
||||||
|| params->capture_orientation != SC_ORIENTATION_0) {
|
|
||||||
if (params->capture_orientation_lock == SC_ORIENTATION_LOCKED_INITIAL) {
|
|
||||||
ADD_PARAM("capture_orientation=@");
|
|
||||||
} else {
|
|
||||||
const char *orient =
|
|
||||||
sc_orientation_get_name(params->capture_orientation);
|
|
||||||
bool locked =
|
|
||||||
params->capture_orientation_lock != SC_ORIENTATION_UNLOCKED;
|
|
||||||
ADD_PARAM("capture_orientation=%s%s", locked ? "@" : "", orient);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (server->tunnel.forward) {
|
if (server->tunnel.forward) {
|
||||||
ADD_PARAM("tunnel_forward=true");
|
ADD_PARAM("tunnel_forward=true");
|
||||||
@ -333,11 +320,6 @@ execute_server(struct sc_server *server,
|
|||||||
if (params->stay_awake) {
|
if (params->stay_awake) {
|
||||||
ADD_PARAM("stay_awake=true");
|
ADD_PARAM("stay_awake=true");
|
||||||
}
|
}
|
||||||
if (params->screen_off_timeout != -1) {
|
|
||||||
assert(params->screen_off_timeout >= 0);
|
|
||||||
uint64_t ms = SC_TICK_TO_MS(params->screen_off_timeout);
|
|
||||||
ADD_PARAM("screen_off_timeout=%" PRIu64, ms);
|
|
||||||
}
|
|
||||||
if (params->video_codec_options) {
|
if (params->video_codec_options) {
|
||||||
VALIDATE_STRING(params->video_codec_options);
|
VALIDATE_STRING(params->video_codec_options);
|
||||||
ADD_PARAM("video_codec_options=%s", params->video_codec_options);
|
ADD_PARAM("video_codec_options=%s", params->video_codec_options);
|
||||||
@ -377,12 +359,6 @@ execute_server(struct sc_server *server,
|
|||||||
VALIDATE_STRING(params->new_display);
|
VALIDATE_STRING(params->new_display);
|
||||||
ADD_PARAM("new_display=%s", params->new_display);
|
ADD_PARAM("new_display=%s", params->new_display);
|
||||||
}
|
}
|
||||||
if (!params->vd_destroy_content) {
|
|
||||||
ADD_PARAM("vd_destroy_content=false");
|
|
||||||
}
|
|
||||||
if (!params->vd_system_decorations) {
|
|
||||||
ADD_PARAM("vd_system_decorations=false");
|
|
||||||
}
|
|
||||||
if (params->list & SC_OPTION_LIST_ENCODERS) {
|
if (params->list & SC_OPTION_LIST_ENCODERS) {
|
||||||
ADD_PARAM("list_encoders=true");
|
ADD_PARAM("list_encoders=true");
|
||||||
}
|
}
|
||||||
@ -404,14 +380,10 @@ execute_server(struct sc_server *server,
|
|||||||
cmd[count++] = NULL;
|
cmd[count++] = NULL;
|
||||||
|
|
||||||
#ifdef SERVER_DEBUGGER
|
#ifdef SERVER_DEBUGGER
|
||||||
LOGI("Server debugger listening%s...",
|
LOGI("Server debugger waiting for a client on device port "
|
||||||
sdk_version < 30 ? " on port " SERVER_DEBUGGER_PORT : "");
|
SERVER_DEBUGGER_PORT "...");
|
||||||
// For Android < 11, from the computer:
|
// From the computer, run
|
||||||
// - run `adb forward tcp:5005 tcp:5005`
|
// adb forward tcp:5005 tcp:5005
|
||||||
// For Android >= 11:
|
|
||||||
// - execute `adb jdwp` to get the jdwp port
|
|
||||||
// - run `adb forward tcp:5005 jdwp:XXXX` (replace XXXX)
|
|
||||||
//
|
|
||||||
// Then, from Android Studio: Run > Debug > Edit configurations...
|
// Then, from Android Studio: Run > Debug > Edit configurations...
|
||||||
// On the left, click on '+', "Remote", with:
|
// On the left, click on '+', "Remote", with:
|
||||||
// Host: localhost
|
// Host: localhost
|
||||||
@ -488,21 +460,14 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params,
|
|||||||
// end of the program
|
// end of the program
|
||||||
server->params = *params;
|
server->params = *params;
|
||||||
|
|
||||||
bool ok = sc_adb_init();
|
bool ok = sc_mutex_init(&server->mutex);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_mutex_init(&server->mutex);
|
|
||||||
if (!ok) {
|
|
||||||
sc_adb_destroy();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = sc_cond_init(&server->cond_stopped);
|
ok = sc_cond_init(&server->cond_stopped);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
sc_mutex_destroy(&server->mutex);
|
sc_mutex_destroy(&server->mutex);
|
||||||
sc_adb_destroy();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -510,7 +475,6 @@ sc_server_init(struct sc_server *server, const struct sc_server_params *params,
|
|||||||
if (!ok) {
|
if (!ok) {
|
||||||
sc_cond_destroy(&server->cond_stopped);
|
sc_cond_destroy(&server->cond_stopped);
|
||||||
sc_mutex_destroy(&server->mutex);
|
sc_mutex_destroy(&server->mutex);
|
||||||
sc_adb_destroy();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -832,14 +796,11 @@ 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;
|
||||||
|
|
||||||
if (disconnect) {
|
|
||||||
// Error expected if not connected, do not report any error
|
// Error expected if not connected, do not report any error
|
||||||
sc_adb_disconnect(intr, ip_port, SC_ADB_SILENT);
|
sc_adb_disconnect(intr, ip_port, SC_ADB_SILENT);
|
||||||
}
|
|
||||||
|
|
||||||
LOGI("Connecting to %s...", ip_port);
|
LOGI("Connecting to %s...", ip_port);
|
||||||
|
|
||||||
@ -855,7 +816,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, bool disconnect) {
|
const char *addr) {
|
||||||
// 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)
|
||||||
@ -866,7 +827,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, disconnect);
|
return sc_server_connect_to_tcpip(server, ip_port);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@ -891,7 +852,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, false);
|
return sc_server_connect_to_tcpip(server, ip_port);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -978,13 +939,7 @@ run_server(void *data) {
|
|||||||
sc_adb_device_destroy(&device);
|
sc_adb_device_destroy(&device);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If the user passed a '+' (--tcpip=+ip), then disconnect first
|
ok = sc_server_configure_tcpip_known_address(server, params->tcpip_dst);
|
||||||
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;
|
||||||
}
|
}
|
||||||
@ -1161,6 +1116,4 @@ sc_server_destroy(struct sc_server *server) {
|
|||||||
sc_intr_destroy(&server->intr);
|
sc_intr_destroy(&server->intr);
|
||||||
sc_cond_destroy(&server->cond_stopped);
|
sc_cond_destroy(&server->cond_stopped);
|
||||||
sc_mutex_destroy(&server->mutex);
|
sc_mutex_destroy(&server->mutex);
|
||||||
|
|
||||||
sc_adb_destroy();
|
|
||||||
}
|
}
|
||||||
|
@ -45,10 +45,7 @@ struct sc_server_params {
|
|||||||
uint32_t video_bit_rate;
|
uint32_t video_bit_rate;
|
||||||
uint32_t audio_bit_rate;
|
uint32_t audio_bit_rate;
|
||||||
const char *max_fps; // float to be parsed by the server
|
const char *max_fps; // float to be parsed by the server
|
||||||
const char *angle; // float to be parsed by the server
|
int8_t lock_video_orientation;
|
||||||
sc_tick screen_off_timeout;
|
|
||||||
enum sc_orientation capture_orientation;
|
|
||||||
enum sc_orientation_lock capture_orientation_lock;
|
|
||||||
bool control;
|
bool control;
|
||||||
uint32_t display_id;
|
uint32_t display_id;
|
||||||
const char *new_display;
|
const char *new_display;
|
||||||
@ -69,8 +66,6 @@ struct sc_server_params {
|
|||||||
bool power_on;
|
bool power_on;
|
||||||
bool kill_adb_on_close;
|
bool kill_adb_on_close;
|
||||||
bool camera_high_speed;
|
bool camera_high_speed;
|
||||||
bool vd_destroy_content;
|
|
||||||
bool vd_system_decorations;
|
|
||||||
uint8_t list;
|
uint8_t list;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6,9 +6,6 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#ifdef __APPLE__
|
|
||||||
# include <mach-o/dyld.h> // for _NSGetExecutablePath()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
@ -63,22 +60,11 @@ sc_file_get_executable_path(void) {
|
|||||||
}
|
}
|
||||||
buf[len] = '\0';
|
buf[len] = '\0';
|
||||||
return strdup(buf);
|
return strdup(buf);
|
||||||
#elif defined(__APPLE__)
|
|
||||||
char buf[PATH_MAX];
|
|
||||||
uint32_t bufsize = PATH_MAX;
|
|
||||||
if (_NSGetExecutablePath(buf, &bufsize) != 0) {
|
|
||||||
LOGE("Executable path buffer too small; need %u bytes", bufsize);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return realpath(buf, NULL);
|
|
||||||
#else
|
#else
|
||||||
// "_" is often used to store the full path of the command being executed
|
// in practice, we only need this feature for portable builds, only used on
|
||||||
char *path = getenv("_");
|
// Windows, so we don't care implementing it for every platform
|
||||||
if (!path) {
|
// (it's useful to have a working version on Linux for debugging though)
|
||||||
LOGE("Could not determine executable path");
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
|
||||||
return strdup(path);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,21 +20,12 @@ struct sc_gamepad_processor {
|
|||||||
struct sc_gamepad_processor_ops {
|
struct sc_gamepad_processor_ops {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process a gamepad device added event
|
* Process a gamepad device added or removed
|
||||||
*
|
*
|
||||||
* This function is mandatory.
|
* This function is mandatory.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
(*process_gamepad_added)(struct sc_gamepad_processor *gp,
|
(*process_gamepad_device)(struct sc_gamepad_processor *gp,
|
||||||
const struct sc_gamepad_device_event *event);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process a gamepad device removed event
|
|
||||||
*
|
|
||||||
* This function is mandatory.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
(*process_gamepad_removed)(struct sc_gamepad_processor *gp,
|
|
||||||
const struct sc_gamepad_device_event *event);
|
const struct sc_gamepad_device_event *event);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,11 +7,6 @@
|
|||||||
/** Downcast gamepad processor to sc_gamepad_uhid */
|
/** Downcast gamepad processor to sc_gamepad_uhid */
|
||||||
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_uhid, gamepad_processor)
|
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_uhid, gamepad_processor)
|
||||||
|
|
||||||
// Xbox 360
|
|
||||||
#define SC_GAMEPAD_UHID_VENDOR_ID UINT16_C(0x045e)
|
|
||||||
#define SC_GAMEPAD_UHID_PRODUCT_ID UINT16_C(0x028e)
|
|
||||||
#define SC_GAMEPAD_UHID_NAME "Microsoft X-Box 360 Pad"
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_gamepad_uhid_send_input(struct sc_gamepad_uhid *gamepad,
|
sc_gamepad_uhid_send_input(struct sc_gamepad_uhid *gamepad,
|
||||||
const struct sc_hid_input *hid_input,
|
const struct sc_hid_input *hid_input,
|
||||||
@ -35,9 +30,7 @@ sc_gamepad_uhid_send_open(struct sc_gamepad_uhid *gamepad,
|
|||||||
struct sc_control_msg msg;
|
struct sc_control_msg msg;
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||||
msg.uhid_create.id = hid_open->hid_id;
|
msg.uhid_create.id = hid_open->hid_id;
|
||||||
msg.uhid_create.vendor_id = SC_GAMEPAD_UHID_VENDOR_ID;
|
msg.uhid_create.name = hid_open->name;
|
||||||
msg.uhid_create.product_id = SC_GAMEPAD_UHID_PRODUCT_ID;
|
|
||||||
msg.uhid_create.name = SC_GAMEPAD_UHID_NAME;
|
|
||||||
msg.uhid_create.report_desc = hid_open->report_desc;
|
msg.uhid_create.report_desc = hid_open->report_desc;
|
||||||
msg.uhid_create.report_desc_size = hid_open->report_desc_size;
|
msg.uhid_create.report_desc_size = hid_open->report_desc_size;
|
||||||
|
|
||||||
@ -59,29 +52,20 @@ sc_gamepad_uhid_send_close(struct sc_gamepad_uhid *gamepad,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_gamepad_processor_process_gamepad_added(struct sc_gamepad_processor *gp,
|
sc_gamepad_processor_process_gamepad_device(struct sc_gamepad_processor *gp,
|
||||||
const struct sc_gamepad_device_event *event) {
|
const struct sc_gamepad_device_event *event) {
|
||||||
struct sc_gamepad_uhid *gamepad = DOWNCAST(gp);
|
struct sc_gamepad_uhid *gamepad = DOWNCAST(gp);
|
||||||
|
|
||||||
|
if (event->type == SC_GAMEPAD_DEVICE_ADDED) {
|
||||||
struct sc_hid_open hid_open;
|
struct sc_hid_open hid_open;
|
||||||
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
|
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
|
||||||
event->gamepad_id)) {
|
event->gamepad_id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_GameController* game_controller =
|
|
||||||
SDL_GameControllerFromInstanceID(event->gamepad_id);
|
|
||||||
assert(game_controller);
|
|
||||||
const char *name = SDL_GameControllerName(game_controller);
|
|
||||||
LOGI("Gamepad added: [%" PRIu32 "] %s", event->gamepad_id, name);
|
|
||||||
|
|
||||||
sc_gamepad_uhid_send_open(gamepad, &hid_open);
|
sc_gamepad_uhid_send_open(gamepad, &hid_open);
|
||||||
}
|
} else {
|
||||||
|
assert(event->type == SC_GAMEPAD_DEVICE_REMOVED);
|
||||||
static void
|
|
||||||
sc_gamepad_processor_process_gamepad_removed(struct sc_gamepad_processor *gp,
|
|
||||||
const struct sc_gamepad_device_event *event) {
|
|
||||||
struct sc_gamepad_uhid *gamepad = DOWNCAST(gp);
|
|
||||||
|
|
||||||
struct sc_hid_close hid_close;
|
struct sc_hid_close hid_close;
|
||||||
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
|
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
|
||||||
@ -89,9 +73,8 @@ sc_gamepad_processor_process_gamepad_removed(struct sc_gamepad_processor *gp,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGI("Gamepad removed: [%" PRIu32 "]", event->gamepad_id);
|
|
||||||
|
|
||||||
sc_gamepad_uhid_send_close(gamepad, &hid_close);
|
sc_gamepad_uhid_send_close(gamepad, &hid_close);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -131,8 +114,7 @@ sc_gamepad_uhid_init(struct sc_gamepad_uhid *gamepad,
|
|||||||
gamepad->controller = controller;
|
gamepad->controller = controller;
|
||||||
|
|
||||||
static const struct sc_gamepad_processor_ops ops = {
|
static const struct sc_gamepad_processor_ops ops = {
|
||||||
.process_gamepad_added = sc_gamepad_processor_process_gamepad_added,
|
.process_gamepad_device = sc_gamepad_processor_process_gamepad_device,
|
||||||
.process_gamepad_removed = sc_gamepad_processor_process_gamepad_removed,
|
|
||||||
.process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis,
|
.process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis,
|
||||||
.process_gamepad_button = sc_gamepad_processor_process_gamepad_button,
|
.process_gamepad_button = sc_gamepad_processor_process_gamepad_button,
|
||||||
};
|
};
|
||||||
|
@ -141,9 +141,7 @@ sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
|||||||
struct sc_control_msg msg;
|
struct sc_control_msg msg;
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||||
msg.uhid_create.id = SC_HID_ID_KEYBOARD;
|
msg.uhid_create.id = SC_HID_ID_KEYBOARD;
|
||||||
msg.uhid_create.vendor_id = 0;
|
msg.uhid_create.name = hid_open.name;
|
||||||
msg.uhid_create.product_id = 0;
|
|
||||||
msg.uhid_create.name = NULL;
|
|
||||||
msg.uhid_create.report_desc = hid_open.report_desc;
|
msg.uhid_create.report_desc = hid_open.report_desc;
|
||||||
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
|
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
|
||||||
if (!sc_controller_push_msg(controller, &msg)) {
|
if (!sc_controller_push_msg(controller, &msg)) {
|
||||||
|
@ -81,9 +81,7 @@ sc_mouse_uhid_init(struct sc_mouse_uhid *mouse,
|
|||||||
struct sc_control_msg msg;
|
struct sc_control_msg msg;
|
||||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||||
msg.uhid_create.id = SC_HID_ID_MOUSE;
|
msg.uhid_create.id = SC_HID_ID_MOUSE;
|
||||||
msg.uhid_create.vendor_id = 0;
|
msg.uhid_create.name = hid_open.name;
|
||||||
msg.uhid_create.product_id = 0;
|
|
||||||
msg.uhid_create.name = NULL;
|
|
||||||
msg.uhid_create.report_desc = hid_open.report_desc;
|
msg.uhid_create.report_desc = hid_open.report_desc;
|
||||||
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
|
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
|
||||||
if (!sc_controller_push_msg(controller, &msg)) {
|
if (!sc_controller_push_msg(controller, &msg)) {
|
||||||
|
@ -7,10 +7,11 @@
|
|||||||
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_aoa, gamepad_processor)
|
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_aoa, gamepad_processor)
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_gamepad_processor_process_gamepad_added(struct sc_gamepad_processor *gp,
|
sc_gamepad_processor_process_gamepad_device(struct sc_gamepad_processor *gp,
|
||||||
const struct sc_gamepad_device_event *event) {
|
const struct sc_gamepad_device_event *event) {
|
||||||
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
|
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
|
||||||
|
|
||||||
|
if (event->type == SC_GAMEPAD_DEVICE_ADDED) {
|
||||||
struct sc_hid_open hid_open;
|
struct sc_hid_open hid_open;
|
||||||
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
|
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
|
||||||
event->gamepad_id)) {
|
event->gamepad_id)) {
|
||||||
@ -21,12 +22,8 @@ sc_gamepad_processor_process_gamepad_added(struct sc_gamepad_processor *gp,
|
|||||||
if (!sc_aoa_push_open(gamepad->aoa, &hid_open, false)) {
|
if (!sc_aoa_push_open(gamepad->aoa, &hid_open, false)) {
|
||||||
LOGW("Could not push AOA HID open (gamepad)");
|
LOGW("Could not push AOA HID open (gamepad)");
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
assert(event->type == SC_GAMEPAD_DEVICE_REMOVED);
|
||||||
static void
|
|
||||||
sc_gamepad_processor_process_gamepad_removed(struct sc_gamepad_processor *gp,
|
|
||||||
const struct sc_gamepad_device_event *event) {
|
|
||||||
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
|
|
||||||
|
|
||||||
struct sc_hid_close hid_close;
|
struct sc_hid_close hid_close;
|
||||||
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
|
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
|
||||||
@ -37,6 +34,7 @@ sc_gamepad_processor_process_gamepad_removed(struct sc_gamepad_processor *gp,
|
|||||||
if (!sc_aoa_push_close(gamepad->aoa, &hid_close)) {
|
if (!sc_aoa_push_close(gamepad->aoa, &hid_close)) {
|
||||||
LOGW("Could not push AOA HID close (gamepad)");
|
LOGW("Could not push AOA HID close (gamepad)");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -78,8 +76,7 @@ sc_gamepad_aoa_init(struct sc_gamepad_aoa *gamepad, struct sc_aoa *aoa) {
|
|||||||
sc_hid_gamepad_init(&gamepad->hid);
|
sc_hid_gamepad_init(&gamepad->hid);
|
||||||
|
|
||||||
static const struct sc_gamepad_processor_ops ops = {
|
static const struct sc_gamepad_processor_ops ops = {
|
||||||
.process_gamepad_added = sc_gamepad_processor_process_gamepad_added,
|
.process_gamepad_device = sc_gamepad_processor_process_gamepad_device,
|
||||||
.process_gamepad_removed = sc_gamepad_processor_process_gamepad_removed,
|
|
||||||
.process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis,
|
.process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis,
|
||||||
.process_gamepad_button = sc_gamepad_processor_process_gamepad_button,
|
.process_gamepad_button = sc_gamepad_processor_process_gamepad_button,
|
||||||
};
|
};
|
||||||
|
@ -95,14 +95,9 @@ scrcpy_otg(struct scrcpy_options *options) {
|
|||||||
// On Windows, only one process could open a USB device
|
// On Windows, only one process could open a USB device
|
||||||
// <https://github.com/Genymobile/scrcpy/issues/2773>
|
// <https://github.com/Genymobile/scrcpy/issues/2773>
|
||||||
LOGI("Killing adb server (if any)...");
|
LOGI("Killing adb server (if any)...");
|
||||||
if (sc_adb_init()) {
|
|
||||||
unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR;
|
unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR;
|
||||||
// uninterruptible (intr == NULL), but in practice it's very quick
|
// uninterruptible (intr == NULL), but in practice it's very quick
|
||||||
sc_adb_kill_server(NULL, flags);
|
sc_adb_kill_server(NULL, flags);
|
||||||
sc_adb_destroy();
|
|
||||||
} else {
|
|
||||||
LOGW("Could not call adb executable, adb server not killed");
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static const struct sc_usb_callbacks cbs = {
|
static const struct sc_usb_callbacks cbs = {
|
||||||
|
@ -175,6 +175,7 @@ sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
|
|||||||
assert(screen->gamepad);
|
assert(screen->gamepad);
|
||||||
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
|
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
|
||||||
|
|
||||||
|
SDL_JoystickID id;
|
||||||
if (event->type == SDL_CONTROLLERDEVICEADDED) {
|
if (event->type == SDL_CONTROLLERDEVICEADDED) {
|
||||||
SDL_GameController *gc = SDL_GameControllerOpen(event->which);
|
SDL_GameController *gc = SDL_GameControllerOpen(event->which);
|
||||||
if (!gc) {
|
if (!gc) {
|
||||||
@ -189,12 +190,9 @@ sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_gamepad_device_event evt = {
|
id = SDL_JoystickInstanceID(joystick);
|
||||||
.gamepad_id = SDL_JoystickInstanceID(joystick),
|
|
||||||
};
|
|
||||||
gp->ops->process_gamepad_added(gp, &evt);
|
|
||||||
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||||
SDL_JoystickID id = event->which;
|
id = event->which;
|
||||||
|
|
||||||
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
|
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
|
||||||
if (gc) {
|
if (gc) {
|
||||||
@ -202,12 +200,16 @@ sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
|
|||||||
} else {
|
} else {
|
||||||
LOGW("Unknown gamepad device removed");
|
LOGW("Unknown gamepad device removed");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
struct sc_gamepad_device_event evt = {
|
struct sc_gamepad_device_event evt = {
|
||||||
|
.type = sc_gamepad_device_event_type_from_sdl_type(event->type),
|
||||||
.gamepad_id = id,
|
.gamepad_id = id,
|
||||||
};
|
};
|
||||||
gp->ops->process_gamepad_removed(gp, &evt);
|
gp->ops->process_gamepad_device(gp, &evt);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
#include "env.h"
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "util/str.h"
|
|
||||||
|
|
||||||
char *
|
|
||||||
sc_get_env(const char *varname) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
wchar_t *w_varname = sc_str_to_wchars(varname);
|
|
||||||
if (!w_varname) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
const wchar_t *value = _wgetenv(w_varname);
|
|
||||||
free(w_varname);
|
|
||||||
if (!value) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sc_str_from_wchars(value);
|
|
||||||
#else
|
|
||||||
const char *value = getenv(varname);
|
|
||||||
if (!value) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return strdup(value);
|
|
||||||
#endif
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
#ifndef SC_ENV_H
|
|
||||||
#define SC_ENV_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
// Return the value of the environment variable (may be NULL).
|
|
||||||
//
|
|
||||||
// The returned value must be freed by the caller.
|
|
||||||
char *
|
|
||||||
sc_get_env(const char *varname);
|
|
||||||
|
|
||||||
#endif
|
|
@ -9,6 +9,8 @@
|
|||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
# include <ws2tcpip.h>
|
# include <ws2tcpip.h>
|
||||||
typedef int socklen_t;
|
typedef int socklen_t;
|
||||||
|
typedef SOCKET sc_raw_socket;
|
||||||
|
# define SC_RAW_SOCKET_NONE INVALID_SOCKET
|
||||||
#else
|
#else
|
||||||
# include <sys/types.h>
|
# include <sys/types.h>
|
||||||
# include <sys/socket.h>
|
# include <sys/socket.h>
|
||||||
@ -21,6 +23,8 @@
|
|||||||
typedef struct sockaddr_in SOCKADDR_IN;
|
typedef struct sockaddr_in SOCKADDR_IN;
|
||||||
typedef struct sockaddr SOCKADDR;
|
typedef struct sockaddr SOCKADDR;
|
||||||
typedef struct in_addr IN_ADDR;
|
typedef struct in_addr IN_ADDR;
|
||||||
|
typedef int sc_raw_socket;
|
||||||
|
# define SC_RAW_SOCKET_NONE -1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@ -43,26 +47,17 @@ net_cleanup(void) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
|
||||||
sc_raw_socket_close(sc_raw_socket raw_sock) {
|
|
||||||
#ifndef _WIN32
|
|
||||||
return !close(raw_sock);
|
|
||||||
#else
|
|
||||||
return !closesocket(raw_sock);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline sc_socket
|
static inline sc_socket
|
||||||
wrap(sc_raw_socket sock) {
|
wrap(sc_raw_socket sock) {
|
||||||
#ifdef SC_SOCKET_CLOSE_ON_INTERRUPT
|
#ifdef _WIN32
|
||||||
if (sock == SC_RAW_SOCKET_NONE) {
|
if (sock == INVALID_SOCKET) {
|
||||||
return SC_SOCKET_NONE;
|
return SC_SOCKET_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_socket_wrapper *socket = malloc(sizeof(*socket));
|
struct sc_socket_windows *socket = malloc(sizeof(*socket));
|
||||||
if (!socket) {
|
if (!socket) {
|
||||||
LOG_OOM();
|
LOG_OOM();
|
||||||
sc_raw_socket_close(sock);
|
closesocket(sock);
|
||||||
return SC_SOCKET_NONE;
|
return SC_SOCKET_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,9 +72,9 @@ wrap(sc_raw_socket sock) {
|
|||||||
|
|
||||||
static inline sc_raw_socket
|
static inline sc_raw_socket
|
||||||
unwrap(sc_socket socket) {
|
unwrap(sc_socket socket) {
|
||||||
#ifdef SC_SOCKET_CLOSE_ON_INTERRUPT
|
#ifdef _WIN32
|
||||||
if (socket == SC_SOCKET_NONE) {
|
if (socket == SC_SOCKET_NONE) {
|
||||||
return SC_RAW_SOCKET_NONE;
|
return INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
return socket->socket;
|
return socket->socket;
|
||||||
@ -88,6 +83,17 @@ unwrap(sc_socket socket) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef HAVE_SOCK_CLOEXEC // avoid unused-function warning
|
||||||
|
static inline bool
|
||||||
|
sc_raw_socket_close(sc_raw_socket raw_sock) {
|
||||||
|
#ifndef _WIN32
|
||||||
|
return !close(raw_sock);
|
||||||
|
#else
|
||||||
|
return !closesocket(raw_sock);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef HAVE_SOCK_CLOEXEC
|
#ifndef HAVE_SOCK_CLOEXEC
|
||||||
// If SOCK_CLOEXEC does not exist, the flag must be set manually once the
|
// If SOCK_CLOEXEC does not exist, the flag must be set manually once the
|
||||||
// socket is created
|
// socket is created
|
||||||
@ -242,9 +248,9 @@ net_interrupt(sc_socket socket) {
|
|||||||
|
|
||||||
sc_raw_socket raw_sock = unwrap(socket);
|
sc_raw_socket raw_sock = unwrap(socket);
|
||||||
|
|
||||||
#ifdef SC_SOCKET_CLOSE_ON_INTERRUPT
|
#ifdef _WIN32
|
||||||
if (!atomic_flag_test_and_set(&socket->closed)) {
|
if (!atomic_flag_test_and_set(&socket->closed)) {
|
||||||
return sc_raw_socket_close(raw_sock);
|
return !closesocket(raw_sock);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
#else
|
#else
|
||||||
@ -256,15 +262,15 @@ bool
|
|||||||
net_close(sc_socket socket) {
|
net_close(sc_socket socket) {
|
||||||
sc_raw_socket raw_sock = unwrap(socket);
|
sc_raw_socket raw_sock = unwrap(socket);
|
||||||
|
|
||||||
#ifdef SC_SOCKET_CLOSE_ON_INTERRUPT
|
#ifdef _WIN32
|
||||||
bool ret = true;
|
bool ret = true;
|
||||||
if (!atomic_flag_test_and_set(&socket->closed)) {
|
if (!atomic_flag_test_and_set(&socket->closed)) {
|
||||||
ret = sc_raw_socket_close(raw_sock);
|
ret = !closesocket(raw_sock);
|
||||||
}
|
}
|
||||||
free(socket);
|
free(socket);
|
||||||
return ret;
|
return ret;
|
||||||
#else
|
#else
|
||||||
return sc_raw_socket_close(raw_sock);
|
return !close(raw_sock);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,36 +7,21 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
# include <winsock2.h>
|
# include <winsock2.h>
|
||||||
typedef SOCKET sc_raw_socket;
|
|
||||||
# define SC_RAW_SOCKET_NONE INVALID_SOCKET
|
|
||||||
#else // not _WIN32
|
|
||||||
# include <sys/socket.h>
|
|
||||||
typedef int sc_raw_socket;
|
|
||||||
# define SC_RAW_SOCKET_NONE -1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(_WIN32) || defined(__APPLE__)
|
|
||||||
// On Windows and macOS, shutdown() does not interrupt accept() or read()
|
|
||||||
// calls, so net_interrupt() must call close() instead, and net_close() must
|
|
||||||
// behave accordingly.
|
|
||||||
// This causes a small race condition (once the socket is closed, its
|
|
||||||
// handle becomes invalid and may in theory be reassigned before another
|
|
||||||
// thread calls accept() or read()), but it is deemed acceptable as a
|
|
||||||
// workaround.
|
|
||||||
# define SC_SOCKET_CLOSE_ON_INTERRUPT
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef SC_SOCKET_CLOSE_ON_INTERRUPT
|
|
||||||
# include <stdatomic.h>
|
# include <stdatomic.h>
|
||||||
# define SC_SOCKET_NONE NULL
|
# define SC_SOCKET_NONE NULL
|
||||||
typedef struct sc_socket_wrapper {
|
typedef struct sc_socket_windows {
|
||||||
sc_raw_socket socket;
|
SOCKET socket;
|
||||||
atomic_flag closed;
|
atomic_flag closed;
|
||||||
} *sc_socket;
|
} *sc_socket;
|
||||||
#else
|
|
||||||
|
#else // not _WIN32
|
||||||
|
|
||||||
|
# include <sys/socket.h>
|
||||||
# define SC_SOCKET_NONE -1
|
# define SC_SOCKET_NONE -1
|
||||||
typedef sc_raw_socket sc_socket;
|
typedef int sc_socket;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define IPV4_LOCALHOST 0x7F000001
|
#define IPV4_LOCALHOST 0x7F000001
|
||||||
|
@ -64,26 +64,6 @@ sc_str_quote(const char *src) {
|
|||||||
return quoted;
|
return quoted;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *
|
|
||||||
sc_str_concat(const char *start, const char *end) {
|
|
||||||
assert(start);
|
|
||||||
assert(end);
|
|
||||||
|
|
||||||
size_t start_len = strlen(start);
|
|
||||||
size_t end_len = strlen(end);
|
|
||||||
|
|
||||||
char *result = malloc(start_len + end_len + 1);
|
|
||||||
if (!result) {
|
|
||||||
LOG_OOM();
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(result, start, start_len);
|
|
||||||
memcpy(result + start_len, end, end_len + 1);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_str_parse_integer(const char *s, long *out) {
|
sc_str_parse_integer(const char *s, long *out) {
|
||||||
char *endptr;
|
char *endptr;
|
||||||
|
@ -38,15 +38,6 @@ sc_str_join(char *dst, const char *const tokens[], char sep, size_t n);
|
|||||||
char *
|
char *
|
||||||
sc_str_quote(const char *src);
|
sc_str_quote(const char *src);
|
||||||
|
|
||||||
/**
|
|
||||||
* Concat two strings
|
|
||||||
*
|
|
||||||
* Return a new allocated string, contanining the concatenation of the two
|
|
||||||
* input strings.
|
|
||||||
*/
|
|
||||||
char *
|
|
||||||
sc_str_concat(const char *start, const char *end);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse `s` as an integer into `out`
|
* Parse `s` as an integer into `out`
|
||||||
*
|
*
|
||||||
|
@ -51,6 +51,7 @@ static void test_options(void) {
|
|||||||
"--fullscreen",
|
"--fullscreen",
|
||||||
"--max-fps", "30",
|
"--max-fps", "30",
|
||||||
"--max-size", "1024",
|
"--max-size", "1024",
|
||||||
|
"--lock-video-orientation=2", // optional arguments require '='
|
||||||
// "--no-control" is not compatible with "--turn-screen-off"
|
// "--no-control" is not compatible with "--turn-screen-off"
|
||||||
// "--no-playback" is not compatible with "--fulscreen"
|
// "--no-playback" is not compatible with "--fulscreen"
|
||||||
"--port", "1234:1236",
|
"--port", "1234:1236",
|
||||||
@ -79,6 +80,7 @@ static void test_options(void) {
|
|||||||
assert(opts->fullscreen);
|
assert(opts->fullscreen);
|
||||||
assert(!strcmp(opts->max_fps, "30"));
|
assert(!strcmp(opts->max_fps, "30"));
|
||||||
assert(opts->max_size == 1024);
|
assert(opts->max_size == 1024);
|
||||||
|
assert(opts->lock_video_orientation == 2);
|
||||||
assert(opts->port_range.first == 1234);
|
assert(opts->port_range.first == 1234);
|
||||||
assert(opts->port_range.last == 1236);
|
assert(opts->port_range.last == 1236);
|
||||||
assert(!strcmp(opts->push_target, "/sdcard/Movies"));
|
assert(!strcmp(opts->push_target, "/sdcard/Movies"));
|
||||||
|
@ -289,11 +289,11 @@ static void test_serialize_set_clipboard_long(void) {
|
|||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_serialize_set_display_power(void) {
|
static void test_serialize_set_screen_power_mode(void) {
|
||||||
struct sc_control_msg msg = {
|
struct sc_control_msg msg = {
|
||||||
.type = SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER,
|
.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
||||||
.set_display_power = {
|
.set_screen_power_mode = {
|
||||||
.on = true,
|
.mode = SC_SCREEN_POWER_MODE_NORMAL,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -302,8 +302,8 @@ static void test_serialize_set_display_power(void) {
|
|||||||
assert(size == 2);
|
assert(size == 2);
|
||||||
|
|
||||||
const uint8_t expected[] = {
|
const uint8_t expected[] = {
|
||||||
SC_CONTROL_MSG_TYPE_SET_DISPLAY_POWER,
|
SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
||||||
0x01, // true
|
0x02, // SC_SCREEN_POWER_MODE_NORMAL
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
@ -329,8 +329,6 @@ static void test_serialize_uhid_create(void) {
|
|||||||
.type = SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
.type = SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||||
.uhid_create = {
|
.uhid_create = {
|
||||||
.id = 42,
|
.id = 42,
|
||||||
.vendor_id = 0x1234,
|
|
||||||
.product_id = 0x5678,
|
|
||||||
.name = "ABC",
|
.name = "ABC",
|
||||||
.report_desc_size = sizeof(report_desc),
|
.report_desc_size = sizeof(report_desc),
|
||||||
.report_desc = report_desc,
|
.report_desc = report_desc,
|
||||||
@ -339,13 +337,11 @@ static void test_serialize_uhid_create(void) {
|
|||||||
|
|
||||||
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
|
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
|
||||||
size_t size = sc_control_msg_serialize(&msg, buf);
|
size_t size = sc_control_msg_serialize(&msg, buf);
|
||||||
assert(size == 24);
|
assert(size == 20);
|
||||||
|
|
||||||
const uint8_t expected[] = {
|
const uint8_t expected[] = {
|
||||||
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||||
0, 42, // id
|
0, 42, // id
|
||||||
0x12, 0x34, // vendor id
|
|
||||||
0x56, 0x78, // product id
|
|
||||||
3, // name size
|
3, // name size
|
||||||
65, 66, 67, // "ABC"
|
65, 66, 67, // "ABC"
|
||||||
0, 11, // report desc size
|
0, 11, // report desc size
|
||||||
@ -411,21 +407,6 @@ static void test_serialize_open_hard_keyboard(void) {
|
|||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_serialize_reset_video(void) {
|
|
||||||
struct sc_control_msg msg = {
|
|
||||||
.type = SC_CONTROL_MSG_TYPE_RESET_VIDEO,
|
|
||||||
};
|
|
||||||
|
|
||||||
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
|
|
||||||
size_t size = sc_control_msg_serialize(&msg, buf);
|
|
||||||
assert(size == 1);
|
|
||||||
|
|
||||||
const uint8_t expected[] = {
|
|
||||||
SC_CONTROL_MSG_TYPE_RESET_VIDEO,
|
|
||||||
};
|
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
(void) argc;
|
(void) argc;
|
||||||
(void) argv;
|
(void) argv;
|
||||||
@ -442,12 +423,11 @@ int main(int argc, char *argv[]) {
|
|||||||
test_serialize_get_clipboard();
|
test_serialize_get_clipboard();
|
||||||
test_serialize_set_clipboard();
|
test_serialize_set_clipboard();
|
||||||
test_serialize_set_clipboard_long();
|
test_serialize_set_clipboard_long();
|
||||||
test_serialize_set_display_power();
|
test_serialize_set_screen_power_mode();
|
||||||
test_serialize_rotate_device();
|
test_serialize_rotate_device();
|
||||||
test_serialize_uhid_create();
|
test_serialize_uhid_create();
|
||||||
test_serialize_uhid_input();
|
test_serialize_uhid_input();
|
||||||
test_serialize_uhid_destroy();
|
test_serialize_uhid_destroy();
|
||||||
test_serialize_open_hard_keyboard();
|
test_serialize_open_hard_keyboard();
|
||||||
test_serialize_reset_video();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -141,16 +141,6 @@ static void test_quote(void) {
|
|||||||
free(out);
|
free(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_concat(void) {
|
|
||||||
const char *s = "2024:11";
|
|
||||||
char *out = sc_str_concat("my-prefix:", s);
|
|
||||||
|
|
||||||
// contains the concat
|
|
||||||
assert(!strcmp("my-prefix:2024:11", out));
|
|
||||||
|
|
||||||
free(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_utf8_truncate(void) {
|
static void test_utf8_truncate(void) {
|
||||||
const char *s = "aÉbÔc";
|
const char *s = "aÉbÔc";
|
||||||
assert(strlen(s) == 7); // É and Ô are 2 bytes-wide
|
assert(strlen(s) == 7); // É and Ô are 2 bytes-wide
|
||||||
@ -399,7 +389,6 @@ int main(int argc, char *argv[]) {
|
|||||||
test_join_truncated_before_sep();
|
test_join_truncated_before_sep();
|
||||||
test_join_truncated_after_sep();
|
test_join_truncated_after_sep();
|
||||||
test_quote();
|
test_quote();
|
||||||
test_concat();
|
|
||||||
test_utf8_truncate();
|
test_utf8_truncate();
|
||||||
test_parse_integer();
|
test_parse_integer();
|
||||||
test_parse_integers();
|
test_parse_integers();
|
||||||
|
@ -170,7 +170,7 @@ latency (for both [video](video.md#buffering) and audio) might be preferable to
|
|||||||
avoid glitches and smooth the playback:
|
avoid glitches and smooth the playback:
|
||||||
|
|
||||||
```
|
```
|
||||||
scrcpy --video-buffer=200 --audio-buffer=200
|
scrcpy --display-buffer=200 --audio-buffer=200
|
||||||
```
|
```
|
||||||
|
|
||||||
It is also possible to configure another audio buffer (the audio output buffer),
|
It is also possible to configure another audio buffer (the audio output buffer),
|
||||||
|
@ -77,7 +77,7 @@ pip3 install meson
|
|||||||
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
|
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
|
||||||
|
|
||||||
# client build dependencies
|
# client build dependencies
|
||||||
sudo dnf install SDL2-devel ffms2-devel libusb1-devel libavdevice-free-devel meson gcc make
|
sudo dnf install SDL2-devel ffms2-devel libusb1-devel meson gcc make
|
||||||
|
|
||||||
# server build dependencies
|
# server build dependencies
|
||||||
sudo dnf install java-devel
|
sudo dnf install java-devel
|
||||||
@ -233,10 +233,10 @@ install` must be run as root)._
|
|||||||
|
|
||||||
#### Option 2: Use prebuilt server
|
#### Option 2: Use prebuilt server
|
||||||
|
|
||||||
- [`scrcpy-server-v3.1`][direct-scrcpy-server]
|
- [`scrcpy-server-v2.7`][direct-scrcpy-server]
|
||||||
<sub>SHA-256: `958f0944a62f23b1f33a16e9eb14844c1a04b882ca175a738c16d23cb22b86c0`</sub>
|
<sub>SHA-256: `a23c5659f36c260f105c022d27bcb3eafffa26070e7baa9eda66d01377a1adba`</sub>
|
||||||
|
|
||||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-server-v3.1
|
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-server-v2.7
|
||||||
|
|
||||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||||
configuration:
|
configuration:
|
||||||
|
@ -85,12 +85,6 @@ 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
|
||||||
|
|
||||||
@ -113,17 +107,16 @@ with the device IP address you found)_.
|
|||||||
7. Run `scrcpy` as usual.
|
7. Run `scrcpy` as usual.
|
||||||
8. Run `adb disconnect` once you're done.
|
8. Run `adb disconnect` once you're done.
|
||||||
|
|
||||||
Since Android 11, a [wireless debugging option][adb-wireless] allows you to
|
Since Android 11, a [wireless debugging option][adb-wireless] allows to bypass
|
||||||
bypass having to physically connect your device to your computer.
|
having to physically connect your device directly to your computer.
|
||||||
|
|
||||||
[adb-wireless]: https://developer.android.com/studio/command-line/adb#wireless-android11-command-line
|
[adb-wireless]: https://developer.android.com/studio/command-line/adb#wireless-android11-command-line
|
||||||
|
|
||||||
|
|
||||||
## Autostart
|
## Autostart
|
||||||
|
|
||||||
A small tool (by the scrcpy author) allows you to run arbitrary commands
|
A small tool (by the scrcpy author) allows to run arbitrary commands whenever a
|
||||||
whenever a new Android device is connected: [AutoAdb]. It can be used to start
|
new Android device is connected: [AutoAdb]. It can be used to start scrcpy:
|
||||||
scrcpy:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
autoadb scrcpy -s '{}'
|
autoadb scrcpy -s '{}'
|
||||||
|
@ -23,20 +23,14 @@ To control the device without mirroring:
|
|||||||
scrcpy --no-video --no-audio
|
scrcpy --no-video --no-audio
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, the mouse is disabled when video playback is turned off.
|
By default, mouse mode is switched to UHID if video mirroring is disabled (a
|
||||||
|
relative mouse mode is required).
|
||||||
To control the device using a relative mouse, enable UHID mouse mode:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --no-video --no-audio --mouse=uhid
|
|
||||||
scrcpy --no-video --no-audio -M # short version
|
|
||||||
```
|
|
||||||
|
|
||||||
To also use a UHID keyboard, set it explicitly:
|
To also use a UHID keyboard, set it explicitly:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --no-video --no-audio --mouse=uhid --keyboard=uhid
|
scrcpy --no-video --no-audio --keyboard=uhid
|
||||||
scrcpy --no-video --no-audio -MK # short version
|
scrcpy --no-video --no-audio -K # short version
|
||||||
```
|
```
|
||||||
|
|
||||||
To use AOA instead (over USB only):
|
To use AOA instead (over USB only):
|
||||||
|
@ -21,9 +21,9 @@ the client and on the server.
|
|||||||
If video is enabled, then the server sends a raw video stream (H.264 by default)
|
If video is enabled, then the server sends a raw video stream (H.264 by default)
|
||||||
of the device screen, with some additional headers for each packet. The client
|
of the device screen, with some additional headers for each packet. The client
|
||||||
decodes the video frames, and displays them as soon as possible, without
|
decodes the video frames, and displays them as soon as possible, without
|
||||||
buffering (unless `--video-buffer=delay` is specified) to minimize latency. The
|
buffering (unless `--display-buffer=delay` is specified) to minimize latency.
|
||||||
client is not aware of the device rotation (which is handled by the server), it
|
The client is not aware of the device rotation (which is handled by the server),
|
||||||
just knows the dimensions of the video frames it receives.
|
it just knows the dimensions of the video frames it receives.
|
||||||
|
|
||||||
Similarly, if audio is enabled, then the server sends a raw audio stream (OPUS
|
Similarly, if audio is enabled, then the server sends a raw audio stream (OPUS
|
||||||
by default) of the device audio output (or the microphone if
|
by default) of the device audio output (or the microphone if
|
||||||
@ -461,30 +461,26 @@ meson setup x -Dserver_debugger=true
|
|||||||
meson configure x -Dserver_debugger=true
|
meson configure x -Dserver_debugger=true
|
||||||
```
|
```
|
||||||
|
|
||||||
Then recompile, and run scrcpy.
|
If your device runs Android 8 or below, set the `server_debugger_method` to
|
||||||
|
`old` in addition:
|
||||||
|
|
||||||
For Android < 11, it will start a debugger on port 5005 on the device and wait:
|
```bash
|
||||||
|
meson setup x -Dserver_debugger=true -Dserver_debugger_method=old
|
||||||
|
# or, if x is already configured
|
||||||
|
meson configure x -Dserver_debugger=true -Dserver_debugger_method=old
|
||||||
|
```
|
||||||
|
|
||||||
|
Then recompile.
|
||||||
|
|
||||||
|
When you start scrcpy, it will start a debugger on port 5005 on the device.
|
||||||
Redirect that port to the computer:
|
Redirect that port to the computer:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
adb forward tcp:5005 tcp:5005
|
adb forward tcp:5005 tcp:5005
|
||||||
```
|
```
|
||||||
|
|
||||||
For Android >= 11, first find the listening port:
|
In Android Studio, _Run_ > _Debug_ > _Edit configurations..._ On the left, click on
|
||||||
|
`+`, _Remote_, and fill the form:
|
||||||
```bash
|
|
||||||
adb jdwp
|
|
||||||
# press Ctrl+C to interrupt
|
|
||||||
```
|
|
||||||
|
|
||||||
Then redirect the resulting PID:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
adb forward tcp:5005 jdwp:XXXX # replace XXXX
|
|
||||||
```
|
|
||||||
|
|
||||||
In Android Studio, _Run_ > _Debug_ > _Edit configurations..._ On the left, click
|
|
||||||
on `+`, _Remote_, and fill the form:
|
|
||||||
|
|
||||||
- Host: `localhost`
|
- Host: `localhost`
|
||||||
- Port: `5005`
|
- Port: `5005`
|
||||||
|
@ -18,46 +18,6 @@ The initial state is restored when _scrcpy_ is closed.
|
|||||||
If the device is not plugged in (i.e. only connected over TCP/IP),
|
If the device is not plugged in (i.e. only connected over TCP/IP),
|
||||||
`--stay-awake` has no effect (this is the Android behavior).
|
`--stay-awake` has no effect (this is the Android behavior).
|
||||||
|
|
||||||
This changes the value of [`stay_on_while_plugged_in`], setting which can be
|
|
||||||
changed manually:
|
|
||||||
|
|
||||||
[`stay_on_while_plugged_in`]: https://developer.android.com/reference/android/provider/Settings.Global#STAY_ON_WHILE_PLUGGED_IN
|
|
||||||
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# get the current show_touches value
|
|
||||||
adb shell settings get global stay_on_while_plugged_in
|
|
||||||
# enable for AC/USB/wireless chargers
|
|
||||||
adb shell settings put global stay_on_while_plugged_in 7
|
|
||||||
# disable
|
|
||||||
adb shell settings put global stay_on_while_plugged_in 0
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Screen off timeout
|
|
||||||
|
|
||||||
The Android screen automatically turns off after some delay.
|
|
||||||
|
|
||||||
To change this delay while scrcpy is running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --screen-off-timeout=300 # 300 seconds (5 minutes)
|
|
||||||
```
|
|
||||||
|
|
||||||
The initial value is restored on exit.
|
|
||||||
|
|
||||||
It is possible to change this setting manually:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# get the current screen_off_timeout value
|
|
||||||
adb shell settings get system screen_off_timeout
|
|
||||||
# set a new value (in milliseconds)
|
|
||||||
adb shell settings put system screen_off_timeout 30000
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that the Android value is in milliseconds, but the scrcpy command line
|
|
||||||
argument is in seconds.
|
|
||||||
|
|
||||||
|
|
||||||
## Turn screen off
|
## Turn screen off
|
||||||
|
|
||||||
@ -86,15 +46,6 @@ scrcpy --turn-screen-off --stay-awake
|
|||||||
scrcpy -Sw # short version
|
scrcpy -Sw # short version
|
||||||
```
|
```
|
||||||
|
|
||||||
Since Android 15, it is possible to change this setting manually:
|
|
||||||
|
|
||||||
```
|
|
||||||
# turn screen off (0 for main display)
|
|
||||||
adb shell cmd display power-off 0
|
|
||||||
# turn screen on
|
|
||||||
adb shell cmd display power-on 0
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Show touches
|
## Show touches
|
||||||
|
|
||||||
@ -111,16 +62,6 @@ scrcpy -t # short version
|
|||||||
|
|
||||||
Note that it only shows _physical_ touches (by a finger on the device).
|
Note that it only shows _physical_ touches (by a finger on the device).
|
||||||
|
|
||||||
It is possible to change this setting manually:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# get the current show_touches value
|
|
||||||
adb shell settings get system show_touches
|
|
||||||
# enable show_touches
|
|
||||||
adb shell settings put system show_touches 1
|
|
||||||
# disable show_touches
|
|
||||||
adb shell settings put system show_touches 0
|
|
||||||
```
|
|
||||||
|
|
||||||
## Power off on close
|
## Power off on close
|
||||||
|
|
||||||
|
23
doc/linux.md
23
doc/linux.md
@ -2,23 +2,6 @@
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
### From the official release
|
|
||||||
|
|
||||||
Download a static build of the [latest release]:
|
|
||||||
|
|
||||||
- [`scrcpy-linux-x86_64-v3.1.tar.gz`][direct-linux-x86_64] (x86_64)
|
|
||||||
<sub>SHA-256: `37dba54092ed9ec6b2f8f95432f61b8ea124aec9f1e9f2b3d22d4b10bb04c59a`</sub>
|
|
||||||
|
|
||||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
|
||||||
[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-linux-x86_64-v3.1.tar.gz
|
|
||||||
|
|
||||||
and extract it.
|
|
||||||
|
|
||||||
_Static builds of scrcpy for Linux are still experimental._
|
|
||||||
|
|
||||||
|
|
||||||
### From your package manager
|
|
||||||
|
|
||||||
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
|
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
|
||||||
|
|
||||||
Scrcpy is packaged in several distributions and package managers:
|
Scrcpy is packaged in several distributions and package managers:
|
||||||
@ -30,10 +13,10 @@ Scrcpy is packaged in several distributions and package managers:
|
|||||||
- Snap: `snap install scrcpy`
|
- Snap: `snap install scrcpy`
|
||||||
- … (see [repology](https://repology.org/project/scrcpy/versions))
|
- … (see [repology](https://repology.org/project/scrcpy/versions))
|
||||||
|
|
||||||
|
### Latest version
|
||||||
|
|
||||||
### From an install script
|
However, the packaged version is not always the latest release. To install the
|
||||||
|
latest release from `master`, follow this simplified process.
|
||||||
To install the latest release from `master`, follow this simplified process.
|
|
||||||
|
|
||||||
First, you need to install the required packages:
|
First, you need to install the required packages:
|
||||||
|
|
||||||
|
23
doc/macos.md
23
doc/macos.md
@ -2,27 +2,6 @@
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
### From the official release
|
|
||||||
|
|
||||||
Download a static build of the [latest release]:
|
|
||||||
|
|
||||||
- [`scrcpy-macos-aarch64-v3.1.tar.gz`][direct-macos-aarch64] (aarch64)
|
|
||||||
<sub>SHA-256: `478618d940421e5f57942f5479d493ecbb38210682937a200f712aee5f235daf`</sub>
|
|
||||||
|
|
||||||
- [`scrcpy-macos-x86_64-v3.1.tar.gz`][direct-macos-x86_64] (x86_64)
|
|
||||||
<sub>SHA-256: `acde98e29c273710ffa469371dbca4a728a44c41c380381f8a54e5b5301b9e87`</sub>
|
|
||||||
|
|
||||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
|
||||||
[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-macos-aarch64-v3.1.tar.gz
|
|
||||||
[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-macos-x86_64-v3.1.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
|
||||||
@ -34,7 +13,7 @@ brew install scrcpy
|
|||||||
You need `adb`, accessible from your `PATH`. If you don't have it yet:
|
You need `adb`, accessible from your `PATH`. If you don't have it yet:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install --cask android-platform-tools
|
brew install android-platform-tools
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, Scrcpy is also available in [MacPorts], which sets up `adb` for you:
|
Alternatively, Scrcpy is also available in [MacPorts], which sets up `adb` for you:
|
||||||
|
12
doc/mouse.md
12
doc/mouse.md
@ -83,9 +83,9 @@ process like the _adb daemon_).
|
|||||||
## Mouse bindings
|
## Mouse bindings
|
||||||
|
|
||||||
By default, with SDK mouse:
|
By default, with SDK mouse:
|
||||||
- right-click triggers `BACK` (or `POWER` on)
|
- right-click triggers BACK (or POWER on)
|
||||||
- middle-click triggers `HOME`
|
- middle-click triggers HOME
|
||||||
- the 4th click triggers `APP_SWITCH`
|
- the 4th click triggers APP_SWITCH
|
||||||
- the 5th click expands the notification panel
|
- the 5th click expands the notification panel
|
||||||
|
|
||||||
The secondary clicks may be forwarded to the device instead by pressing the
|
The secondary clicks may be forwarded to the device instead by pressing the
|
||||||
@ -121,9 +121,9 @@ Each character must be one of the following:
|
|||||||
|
|
||||||
- `+`: forward the click to the device
|
- `+`: forward the click to the device
|
||||||
- `-`: ignore the click
|
- `-`: ignore the click
|
||||||
- `b`: trigger shortcut `BACK` (or turn screen on if off)
|
- `b`: trigger shortcut BACK (or turn screen on if off)
|
||||||
- `h`: trigger shortcut `HOME`
|
- `h`: trigger shortcut HOME
|
||||||
- `s`: trigger shortcut `APP_SWITCH`
|
- `s`: trigger shortcut APP_SWITCH
|
||||||
- `n`: trigger shortcut "expand notification panel"
|
- `n`: trigger shortcut "expand notification panel"
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
@ -30,7 +30,6 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
|||||||
| Flip display vertically | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>↑</kbd> _(up)_ \| <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>↓</kbd> _(down)_
|
| Flip display vertically | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>↑</kbd> _(up)_ \| <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>↓</kbd> _(down)_
|
||||||
| Pause or re-pause display | <kbd>MOD</kbd>+<kbd>z</kbd>
|
| Pause or re-pause display | <kbd>MOD</kbd>+<kbd>z</kbd>
|
||||||
| Unpause display | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>z</kbd>
|
| Unpause display | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>z</kbd>
|
||||||
| Reset video capture/encoding | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>r</kbd>
|
|
||||||
| Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
| Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
||||||
| Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_
|
| Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_
|
||||||
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
|
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
|
||||||
|
69
doc/video.md
69
doc/video.md
@ -27,9 +27,6 @@ preserved. That way, a device in 1920×1080 will be mirrored at 1024×576.
|
|||||||
If encoding fails, scrcpy automatically tries again with a lower definition
|
If encoding fails, scrcpy automatically tries again with a lower definition
|
||||||
(unless `--no-downsize-on-error` is enabled).
|
(unless `--no-downsize-on-error` is enabled).
|
||||||
|
|
||||||
For camera mirroring, the `--max-size` value is used to select the camera source
|
|
||||||
size instead (among the available resolutions).
|
|
||||||
|
|
||||||
|
|
||||||
## Bit rate
|
## Bit rate
|
||||||
|
|
||||||
@ -96,7 +93,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'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -106,45 +103,24 @@ The orientation may be applied at 3 different levels:
|
|||||||
- The [shortcut](shortcuts.md) <kbd>MOD</kbd>+<kbd>r</kbd> requests the
|
- The [shortcut](shortcuts.md) <kbd>MOD</kbd>+<kbd>r</kbd> requests the
|
||||||
device to switch between portrait and landscape (the current running app may
|
device to switch between portrait and landscape (the current running app may
|
||||||
refuse, if it does not support the requested orientation).
|
refuse, if it does not support the requested orientation).
|
||||||
- `--capture-orientation` changes the mirroring orientation (the orientation
|
- `--lock-video-orientation` changes the mirroring orientation (the orientation
|
||||||
of the video sent from the device to the computer). This affects the
|
of the video sent from the device to the computer). This affects the
|
||||||
recording.
|
recording.
|
||||||
- `--orientation` is applied on the client side, and affects display and
|
- `--orientation` is applied on the client side, and affects display and
|
||||||
recording. For the display, it can be changed dynamically using
|
recording. For the display, it can be changed dynamically using
|
||||||
[shortcuts](shortcuts.md).
|
[shortcuts](shortcuts.md).
|
||||||
|
|
||||||
To capture the video with a specific orientation:
|
To lock the mirroring orientation (on the capture side):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --capture-orientation=0
|
scrcpy --lock-video-orientation # initial (current) orientation
|
||||||
scrcpy --capture-orientation=90 # 90° clockwise
|
scrcpy --lock-video-orientation=0 # natural orientation
|
||||||
scrcpy --capture-orientation=180 # 180°
|
scrcpy --lock-video-orientation=90 # 90° clockwise
|
||||||
scrcpy --capture-orientation=270 # 270° clockwise
|
scrcpy --lock-video-orientation=180 # 180°
|
||||||
scrcpy --capture-orientation=flip0 # hflip
|
scrcpy --lock-video-orientation=270 # 270° clockwise
|
||||||
scrcpy --capture-orientation=flip90 # hflip + 90° clockwise
|
|
||||||
scrcpy --capture-orientation=flip180 # hflip + 180°
|
|
||||||
scrcpy --capture-orientation=flip270 # hflip + 270° clockwise
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The capture orientation can be locked by using `@`, so that a physical device
|
To orient the video (on the rendering side):
|
||||||
rotation does not change the captured video orientation:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --capture-orientation=@ # locked to the initial orientation
|
|
||||||
scrcpy --capture-orientation=@0 # locked to 0°
|
|
||||||
scrcpy --capture-orientation=@90 # locked to 90° clockwise
|
|
||||||
scrcpy --capture-orientation=@180 # locked to 180°
|
|
||||||
scrcpy --capture-orientation=@270 # locked to 270° clockwise
|
|
||||||
scrcpy --capture-orientation=@flip0 # locked to hflip
|
|
||||||
scrcpy --capture-orientation=@flip90 # locked to hflip + 90° clockwise
|
|
||||||
scrcpy --capture-orientation=@flip180 # locked to hflip + 180°
|
|
||||||
scrcpy --capture-orientation=@flip270 # locked to hflip + 270° clockwise
|
|
||||||
```
|
|
||||||
|
|
||||||
The capture orientation transform is applied after `--crop`, but before
|
|
||||||
`--angle`.
|
|
||||||
|
|
||||||
To orient the video (on the client side):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --orientation=0
|
scrcpy --orientation=0
|
||||||
@ -165,19 +141,6 @@ to the MP4 or MKV target file. Flipping is not supported, so only the 4 first
|
|||||||
values are allowed when recording.
|
values are allowed when recording.
|
||||||
|
|
||||||
|
|
||||||
## Angle
|
|
||||||
|
|
||||||
To rotate the video content by a custom angle (in degrees, clockwise):
|
|
||||||
|
|
||||||
```
|
|
||||||
scrcpy --angle=23
|
|
||||||
```
|
|
||||||
|
|
||||||
The center of rotation is the center of the visible area.
|
|
||||||
|
|
||||||
This transformation is applied after `--crop` and `--capture-orientation`.
|
|
||||||
|
|
||||||
|
|
||||||
## Crop
|
## Crop
|
||||||
|
|
||||||
The device screen may be cropped to mirror only part of the screen.
|
The device screen may be cropped to mirror only part of the screen.
|
||||||
@ -191,11 +154,7 @@ scrcpy --crop=1224:1440:0:0 # 1224x1440 at offset (0,0)
|
|||||||
The values are expressed in the device natural orientation (portrait for a
|
The values are expressed in the device natural orientation (portrait for a
|
||||||
phone, landscape for a tablet).
|
phone, landscape for a tablet).
|
||||||
|
|
||||||
Cropping is performed before `--capture-orientation` and `--angle`.
|
If `--max-size` is also specified, resizing is applied after cropping.
|
||||||
|
|
||||||
For display mirroring, `--max-size` is applied after cropping. For camera,
|
|
||||||
`--max-size` is applied first (because it selects the source size rather than
|
|
||||||
resizing the content).
|
|
||||||
|
|
||||||
|
|
||||||
## Display
|
## Display
|
||||||
@ -216,8 +175,6 @@ scrcpy --list-displays
|
|||||||
A secondary display may only be controlled if the device runs at least Android
|
A secondary display may only be controlled if the device runs at least Android
|
||||||
10 (otherwise it is mirrored as read-only).
|
10 (otherwise it is mirrored as read-only).
|
||||||
|
|
||||||
It is also possible to create a [virtual display](virtual_display.md).
|
|
||||||
|
|
||||||
|
|
||||||
## Buffering
|
## Buffering
|
||||||
|
|
||||||
@ -232,15 +189,15 @@ The configuration is available independently for the display,
|
|||||||
[v4l2 sinks](video.md#video4linux) and [audio](audio.md#buffering) playback.
|
[v4l2 sinks](video.md#video4linux) and [audio](audio.md#buffering) playback.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --video-buffer=50 # add 50ms buffering for video playback
|
scrcpy --display-buffer=50 # add 50ms buffering for display
|
||||||
scrcpy --audio-buffer=200 # set 200ms buffering for audio playback
|
|
||||||
scrcpy --v4l2-buffer=300 # add 300ms buffering for v4l2 sink
|
scrcpy --v4l2-buffer=300 # add 300ms buffering for v4l2 sink
|
||||||
|
scrcpy --audio-buffer=200 # set 200ms buffering for audio playback
|
||||||
```
|
```
|
||||||
|
|
||||||
They can be applied simultaneously:
|
They can be applied simultaneously:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --video-buffer=50 --v4l2-buffer=300
|
scrcpy --display-buffer=50 --v4l2-buffer=300
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ To mirror a new virtual display instead of the device screen:
|
|||||||
scrcpy --new-display=1920x1080
|
scrcpy --new-display=1920x1080
|
||||||
scrcpy --new-display=1920x1080/420 # force 420 dpi
|
scrcpy --new-display=1920x1080/420 # force 420 dpi
|
||||||
scrcpy --new-display # use the main display size and density
|
scrcpy --new-display # use the main display size and density
|
||||||
|
scrcpy --new-display -m1920 # ... scaled to fit a max size of 1920
|
||||||
scrcpy --new-display=/240 # use the main display size and 240 dpi
|
scrcpy --new-display=/240 # use the main display size and 240 dpi
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -15,49 +16,11 @@ scrcpy --new-display=/240 # use the main display size and 240 dpi
|
|||||||
|
|
||||||
On some devices, a launcher is available in the virtual display.
|
On some devices, a launcher is available in the virtual display.
|
||||||
|
|
||||||
When no launcher is available (or if is explicitly disabled by
|
When no launcher is available, the virtual display is empty. In that case, you
|
||||||
[`--no-vd-system-decorations`](#system-decorations)), the virtual display is
|
must [start an Android app](device.md#start-android-app).
|
||||||
empty. In that case, you must [start an Android
|
|
||||||
app](device.md#start-android-app).
|
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
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
|
|
||||||
|
|
||||||
By default, virtual display system decorations are enabled. To disable them, use
|
|
||||||
`--no-vd-system-decorations`:
|
|
||||||
|
|
||||||
```
|
|
||||||
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
|
|
||||||
will be produced at all.
|
|
||||||
|
|
||||||
|
|
||||||
## Destroy on close
|
|
||||||
|
|
||||||
By default, when the virtual display is closed, the running apps are destroyed.
|
|
||||||
|
|
||||||
To move them to the main display instead, use:
|
|
||||||
|
|
||||||
```
|
|
||||||
scrcpy --new-display --no-vd-destroy-content
|
|
||||||
```
|
|
||||||
|
@ -2,45 +2,35 @@
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
### From the official release
|
|
||||||
|
|
||||||
Download the [latest release]:
|
Download the [latest release]:
|
||||||
|
|
||||||
- [`scrcpy-win64-v3.1.zip`][direct-win64] (64-bit)
|
- [`scrcpy-win64-v2.7.zip`][direct-win64] (64-bit)
|
||||||
<sub>SHA-256: `0c05ea395d95cfe36bee974eeb435a3db87ea5594ff738370d5dc3068a9538ca`</sub>
|
<sub>SHA-256: `5910bc18d5a16f42d84185ddc7e16a4cee6a6f5f33451559c1a1d6d0099bd5f5`</sub>
|
||||||
- [`scrcpy-win32-v3.1.zip`][direct-win32] (32-bit)
|
- [`scrcpy-win32-v2.7.zip`][direct-win32] (32-bit)
|
||||||
<sub>SHA-256: `2b4674ef76719680ac5a9b482d1943bdde3fa25821ad2e98f3c40c347d00d560`</sub>
|
<sub>SHA-256: `ef4daf89d500f33d78b830625536ecb18481429dd94433e7634c824292059d06`</sub>
|
||||||
|
|
||||||
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
|
||||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-win64-v3.1.zip
|
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-win64-v2.7.zip
|
||||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-win32-v3.1.zip
|
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-win32-v2.7.zip
|
||||||
|
|
||||||
and extract it.
|
and extract it.
|
||||||
|
|
||||||
|
Alternatively, you could install it from packages manager, like [Chocolatey]:
|
||||||
### From a package manager
|
|
||||||
|
|
||||||
From [WinGet] (ADB and other dependencies will be installed alongside scrcpy):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
winget install --exact Genymobile.scrcpy
|
|
||||||
```
|
|
||||||
|
|
||||||
From [Chocolatey]:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
choco install scrcpy
|
choco install scrcpy
|
||||||
choco install adb # if you don't have it yet
|
choco install adb # if you don't have it yet
|
||||||
```
|
```
|
||||||
|
|
||||||
From [Scoop]:
|
or [Scoop]:
|
||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scoop install scrcpy
|
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
|
[Winget]: https://github.com/microsoft/winget-cli
|
||||||
[Chocolatey]: https://chocolatey.org/
|
[Chocolatey]: https://chocolatey.org/
|
||||||
[Scoop]: https://scoop.sh
|
[Scoop]: https://scoop.sh
|
||||||
|
|
||||||
|
@ -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.1/scrcpy-server-v3.1
|
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-server-v2.7
|
||||||
PREBUILT_SERVER_SHA256=958f0944a62f23b1f33a16e9eb14844c1a04b882ca175a738c16d23cb22b86c0
|
PREBUILT_SERVER_SHA256=a23c5659f36c260f105c022d27bcb3eafffa26070e7baa9eda66d01377a1adba
|
||||||
|
|
||||||
echo "[scrcpy] Downloading prebuilt server..."
|
echo "[scrcpy] Downloading prebuilt server..."
|
||||||
wget "$PREBUILT_SERVER_URL" -O scrcpy-server
|
wget "$PREBUILT_SERVER_URL" -O scrcpy-server
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
project('scrcpy', 'c',
|
project('scrcpy', 'c',
|
||||||
version: '3.1',
|
version: '2.7',
|
||||||
meson_version: '>= 0.48',
|
meson_version: '>= 0.48',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c11',
|
'c_std=c11',
|
||||||
|
@ -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')
|
||||||
|
141
release.mk
Normal file
141
release.mk
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
# This makefile provides recipes to build a "portable" version of scrcpy for
|
||||||
|
# Windows.
|
||||||
|
#
|
||||||
|
# Here, "portable" means that the client and server binaries are expected to be
|
||||||
|
# anywhere, but in the same directory, instead of well-defined separate
|
||||||
|
# locations (e.g. /usr/bin/scrcpy and /usr/share/scrcpy/scrcpy-server).
|
||||||
|
#
|
||||||
|
# In particular, this implies to change the location from where the client push
|
||||||
|
# the server to the device.
|
||||||
|
|
||||||
|
.PHONY: default clean \
|
||||||
|
test test-client test-server \
|
||||||
|
build-server \
|
||||||
|
prepare-deps-win32 prepare-deps-win64 \
|
||||||
|
build-win32 build-win64 \
|
||||||
|
zip-win32 zip-win64 \
|
||||||
|
package release
|
||||||
|
|
||||||
|
GRADLE ?= ./gradlew
|
||||||
|
|
||||||
|
TEST_BUILD_DIR := build-test
|
||||||
|
SERVER_BUILD_DIR := build-server
|
||||||
|
WIN32_BUILD_DIR := build-win32
|
||||||
|
WIN64_BUILD_DIR := build-win64
|
||||||
|
|
||||||
|
VERSION ?= $(shell git describe --tags --exclude='*install-release' --always)
|
||||||
|
|
||||||
|
ZIP := zip
|
||||||
|
WIN32_TARGET_DIR := scrcpy-win32-$(VERSION)
|
||||||
|
WIN64_TARGET_DIR := scrcpy-win64-$(VERSION)
|
||||||
|
WIN32_TARGET := $(WIN32_TARGET_DIR).zip
|
||||||
|
WIN64_TARGET := $(WIN64_TARGET_DIR).zip
|
||||||
|
|
||||||
|
RELEASE_DIR := release-$(VERSION)
|
||||||
|
|
||||||
|
release: clean test build-server build-win32 build-win64 package
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(GRADLE) clean
|
||||||
|
rm -rf "$(ZIP)" "$(TEST_BUILD_DIR)" "$(SERVER_BUILD_DIR)" \
|
||||||
|
"$(WIN32_BUILD_DIR)" "$(WIN64_BUILD_DIR)"
|
||||||
|
|
||||||
|
test-client:
|
||||||
|
[ -d "$(TEST_BUILD_DIR)" ] || ( mkdir "$(TEST_BUILD_DIR)" && \
|
||||||
|
meson setup "$(TEST_BUILD_DIR)" -Db_sanitize=address )
|
||||||
|
ninja -C "$(TEST_BUILD_DIR)"
|
||||||
|
|
||||||
|
test-server:
|
||||||
|
$(GRADLE) -p server check
|
||||||
|
|
||||||
|
test: test-client test-server
|
||||||
|
|
||||||
|
build-server:
|
||||||
|
$(GRADLE) -p server assembleRelease
|
||||||
|
mkdir -p "$(SERVER_BUILD_DIR)/server"
|
||||||
|
cp server/build/outputs/apk/release/server-release-unsigned.apk \
|
||||||
|
"$(SERVER_BUILD_DIR)/server/scrcpy-server"
|
||||||
|
|
||||||
|
prepare-deps-win32:
|
||||||
|
@app/deps/adb.sh win32
|
||||||
|
@app/deps/sdl.sh win32
|
||||||
|
@app/deps/ffmpeg.sh win32
|
||||||
|
@app/deps/libusb.sh win32
|
||||||
|
|
||||||
|
prepare-deps-win64:
|
||||||
|
@app/deps/adb.sh win64
|
||||||
|
@app/deps/sdl.sh win64
|
||||||
|
@app/deps/ffmpeg.sh win64
|
||||||
|
@app/deps/libusb.sh win64
|
||||||
|
|
||||||
|
build-win32: prepare-deps-win32
|
||||||
|
rm -rf "$(WIN32_BUILD_DIR)"
|
||||||
|
mkdir -p "$(WIN32_BUILD_DIR)/local"
|
||||||
|
meson setup "$(WIN32_BUILD_DIR)" \
|
||||||
|
--pkg-config-path="app/deps/work/install/win32/lib/pkgconfig" \
|
||||||
|
-Dc_args="-I$(PWD)/app/deps/work/install/win32/include" \
|
||||||
|
-Dc_link_args="-L$(PWD)/app/deps/work/install/win32/lib" \
|
||||||
|
--cross-file=cross_win32.txt \
|
||||||
|
--buildtype=release --strip -Db_lto=true \
|
||||||
|
-Dcompile_server=false \
|
||||||
|
-Dportable=true
|
||||||
|
ninja -C "$(WIN32_BUILD_DIR)"
|
||||||
|
# Group intermediate outputs into a 'dist' directory
|
||||||
|
mkdir -p "$(WIN32_BUILD_DIR)/dist"
|
||||||
|
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(WIN32_BUILD_DIR)/dist/"
|
||||||
|
cp app/data/scrcpy-console.bat "$(WIN32_BUILD_DIR)/dist/"
|
||||||
|
cp app/data/scrcpy-noconsole.vbs "$(WIN32_BUILD_DIR)/dist/"
|
||||||
|
cp app/data/icon.png "$(WIN32_BUILD_DIR)/dist/"
|
||||||
|
cp app/data/open_a_terminal_here.bat "$(WIN32_BUILD_DIR)/dist/"
|
||||||
|
cp app/deps/work/install/win32/bin/*.dll "$(WIN32_BUILD_DIR)/dist/"
|
||||||
|
cp app/deps/work/install/win32/bin/adb.exe "$(WIN32_BUILD_DIR)/dist/"
|
||||||
|
|
||||||
|
build-win64: prepare-deps-win64
|
||||||
|
rm -rf "$(WIN64_BUILD_DIR)"
|
||||||
|
mkdir -p "$(WIN64_BUILD_DIR)/local"
|
||||||
|
meson setup "$(WIN64_BUILD_DIR)" \
|
||||||
|
--pkg-config-path="app/deps/work/install/win64/lib/pkgconfig" \
|
||||||
|
-Dc_args="-I$(PWD)/app/deps/work/install/win64/include" \
|
||||||
|
-Dc_link_args="-L$(PWD)/app/deps/work/install/win64/lib" \
|
||||||
|
--cross-file=cross_win64.txt \
|
||||||
|
--buildtype=release --strip -Db_lto=true \
|
||||||
|
-Dcompile_server=false \
|
||||||
|
-Dportable=true
|
||||||
|
ninja -C "$(WIN64_BUILD_DIR)"
|
||||||
|
# Group intermediate outputs into a 'dist' directory
|
||||||
|
mkdir -p "$(WIN64_BUILD_DIR)/dist"
|
||||||
|
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(WIN64_BUILD_DIR)/dist/"
|
||||||
|
cp app/data/scrcpy-console.bat "$(WIN64_BUILD_DIR)/dist/"
|
||||||
|
cp app/data/scrcpy-noconsole.vbs "$(WIN64_BUILD_DIR)/dist/"
|
||||||
|
cp app/data/icon.png "$(WIN64_BUILD_DIR)/dist/"
|
||||||
|
cp app/data/open_a_terminal_here.bat "$(WIN64_BUILD_DIR)/dist/"
|
||||||
|
cp app/deps/work/install/win64/bin/*.dll "$(WIN64_BUILD_DIR)/dist/"
|
||||||
|
cp app/deps/work/install/win64/bin/adb.exe "$(WIN64_BUILD_DIR)/dist/"
|
||||||
|
|
||||||
|
zip-win32:
|
||||||
|
mkdir -p "$(ZIP)/$(WIN32_TARGET_DIR)"
|
||||||
|
cp -r "$(WIN32_BUILD_DIR)/dist/." "$(ZIP)/$(WIN32_TARGET_DIR)/"
|
||||||
|
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(ZIP)/$(WIN32_TARGET_DIR)/"
|
||||||
|
cd "$(ZIP)"; \
|
||||||
|
zip -r "$(WIN32_TARGET)" "$(WIN32_TARGET_DIR)"
|
||||||
|
rm -rf "$(ZIP)/$(WIN32_TARGET_DIR)"
|
||||||
|
|
||||||
|
zip-win64:
|
||||||
|
mkdir -p "$(ZIP)/$(WIN64_TARGET_DIR)"
|
||||||
|
cp -r "$(WIN64_BUILD_DIR)/dist/." "$(ZIP)/$(WIN64_TARGET_DIR)/"
|
||||||
|
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(ZIP)/$(WIN64_TARGET_DIR)/"
|
||||||
|
cd "$(ZIP)"; \
|
||||||
|
zip -r "$(WIN64_TARGET)" "$(WIN64_TARGET_DIR)"
|
||||||
|
rm -rf "$(ZIP)/$(WIN64_TARGET_DIR)"
|
||||||
|
|
||||||
|
package: zip-win32 zip-win64
|
||||||
|
mkdir -p "$(RELEASE_DIR)"
|
||||||
|
cp "$(SERVER_BUILD_DIR)/server/scrcpy-server" \
|
||||||
|
"$(RELEASE_DIR)/scrcpy-server-$(VERSION)"
|
||||||
|
cp "$(ZIP)/$(WIN32_TARGET)" "$(RELEASE_DIR)"
|
||||||
|
cp "$(ZIP)/$(WIN64_TARGET)" "$(RELEASE_DIR)"
|
||||||
|
cd "$(RELEASE_DIR)" && \
|
||||||
|
sha256sum "scrcpy-server-$(VERSION)" \
|
||||||
|
"scrcpy-win32-$(VERSION).zip" \
|
||||||
|
"scrcpy-win64-$(VERSION).zip" > SHA256SUMS.txt
|
||||||
|
@echo "Release generated in $(RELEASE_DIR)/"
|
2
release.sh
Executable file
2
release.sh
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
make -f release.mk
|
2
release/.gitignore
vendored
2
release/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
/work
|
|
||||||
/output
|
|
@ -1,5 +0,0 @@
|
|||||||
# This file must be sourced from the release scripts directory
|
|
||||||
WORK_DIR="$PWD/work"
|
|
||||||
OUTPUT_DIR="$PWD/output"
|
|
||||||
|
|
||||||
VERSION="${VERSION:-$(git describe --tags --always)}"
|
|
@ -1,43 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -ex
|
|
||||||
cd "$(dirname ${BASH_SOURCE[0]})"
|
|
||||||
. build_common
|
|
||||||
cd .. # root project dir
|
|
||||||
|
|
||||||
if [[ $# != 1 ]]
|
|
||||||
then
|
|
||||||
echo "Syntax: $0 <arch>" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
ARCH="$1"
|
|
||||||
LINUX_BUILD_DIR="$WORK_DIR/build-linux-$ARCH"
|
|
||||||
|
|
||||||
app/deps/adb_linux.sh
|
|
||||||
app/deps/sdl.sh linux native static
|
|
||||||
app/deps/dav1d.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/"
|
|
@ -1,43 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -ex
|
|
||||||
cd "$(dirname ${BASH_SOURCE[0]})"
|
|
||||||
. build_common
|
|
||||||
cd .. # root project dir
|
|
||||||
|
|
||||||
if [[ $# != 1 ]]
|
|
||||||
then
|
|
||||||
echo "Syntax: $0 <arch>" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
ARCH="$1"
|
|
||||||
MACOS_BUILD_DIR="$WORK_DIR/build-macos-$ARCH"
|
|
||||||
|
|
||||||
app/deps/adb_macos.sh
|
|
||||||
app/deps/sdl.sh macos native static
|
|
||||||
app/deps/dav1d.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/"
|
|
@ -1,14 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -ex
|
|
||||||
cd "$(dirname ${BASH_SOURCE[0]})"
|
|
||||||
. build_common
|
|
||||||
cd .. # root project dir
|
|
||||||
|
|
||||||
GRADLE="${GRADLE:-./gradlew}"
|
|
||||||
SERVER_BUILD_DIR="$WORK_DIR/build-server"
|
|
||||||
|
|
||||||
rm -rf "$SERVER_BUILD_DIR"
|
|
||||||
"$GRADLE" -p server assembleRelease
|
|
||||||
mkdir -p "$SERVER_BUILD_DIR/server"
|
|
||||||
cp server/build/outputs/apk/release/server-release-unsigned.apk \
|
|
||||||
"$SERVER_BUILD_DIR/server/scrcpy-server"
|
|
@ -1,53 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -ex
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
32)
|
|
||||||
WINXX=win32
|
|
||||||
;;
|
|
||||||
64)
|
|
||||||
WINXX=win64
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "ERROR: $0 must be called with one argument: 32 or 64" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
cd "$(dirname ${BASH_SOURCE[0]})"
|
|
||||||
. build_common
|
|
||||||
cd .. # root project dir
|
|
||||||
|
|
||||||
WINXX_BUILD_DIR="$WORK_DIR/build-$WINXX"
|
|
||||||
|
|
||||||
app/deps/adb_windows.sh
|
|
||||||
app/deps/sdl.sh $WINXX cross shared
|
|
||||||
app/deps/dav1d.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/"
|
|
@ -1,14 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -ex
|
|
||||||
cd "$(dirname ${BASH_SOURCE[0]})"
|
|
||||||
. build_common
|
|
||||||
|
|
||||||
cd "$OUTPUT_DIR"
|
|
||||||
sha256sum "scrcpy-server-$VERSION" \
|
|
||||||
"scrcpy-linux-x86_64-$VERSION.tar.gz" \
|
|
||||||
"scrcpy-win32-$VERSION.zip" \
|
|
||||||
"scrcpy-win64-$VERSION.zip" \
|
|
||||||
"scrcpy-macos-aarch64-$VERSION.tar.gz" \
|
|
||||||
"scrcpy-macos-x86_64-$VERSION.tar.gz" \
|
|
||||||
| tee SHA256SUMS.txt
|
|
||||||
echo "Release checksums generated in $PWD/SHA256SUMS.txt"
|
|
@ -1,52 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -ex
|
|
||||||
cd "$(dirname ${BASH_SOURCE[0]})"
|
|
||||||
. build_common
|
|
||||||
cd .. # root project dir
|
|
||||||
|
|
||||||
if [[ $# != 2 ]]
|
|
||||||
then
|
|
||||||
# <target_name>: for example win64
|
|
||||||
# <format>: zip or tar.gz
|
|
||||||
echo "Syntax: $0 <target> <format>" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
FORMAT=$2
|
|
||||||
|
|
||||||
if [[ "$FORMAT" != zip && "$FORMAT" != tar.gz ]]
|
|
||||||
then
|
|
||||||
echo "Invalid format (expected zip or tar.gz): $FORMAT" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
BUILD_DIR="$WORK_DIR/build-$1"
|
|
||||||
ARCHIVE_DIR="$BUILD_DIR/release-archive"
|
|
||||||
TARGET_DIRNAME="scrcpy-$1-$VERSION"
|
|
||||||
|
|
||||||
rm -rf "$ARCHIVE_DIR/$TARGET_DIRNAME"
|
|
||||||
mkdir -p "$ARCHIVE_DIR/$TARGET_DIRNAME"
|
|
||||||
|
|
||||||
cp -r "$BUILD_DIR/dist/." "$ARCHIVE_DIR/$TARGET_DIRNAME/"
|
|
||||||
cp "$WORK_DIR/build-server/server/scrcpy-server" "$ARCHIVE_DIR/$TARGET_DIRNAME/"
|
|
||||||
|
|
||||||
mkdir -p "$OUTPUT_DIR"
|
|
||||||
|
|
||||||
cd "$ARCHIVE_DIR"
|
|
||||||
rm -f "$OUTPUT_DIR/$TARGET_DIRNAME.$FORMAT"
|
|
||||||
|
|
||||||
case "$FORMAT" in
|
|
||||||
zip)
|
|
||||||
zip -r "$OUTPUT_DIR/$TARGET_DIRNAME.zip" "$TARGET_DIRNAME"
|
|
||||||
;;
|
|
||||||
tar.gz)
|
|
||||||
tar cvzf "$OUTPUT_DIR/$TARGET_DIRNAME.tar.gz" "$TARGET_DIRNAME"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Invalid format (expected zip or tar.gz): $FORMAT" >&2
|
|
||||||
exit 1
|
|
||||||
esac
|
|
||||||
|
|
||||||
rm -rf "$TARGET_DIRNAME"
|
|
||||||
cd -
|
|
||||||
echo "Generated '$OUTPUT_DIR/$TARGET_DIRNAME.$FORMAT'"
|
|
@ -1,10 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -ex
|
|
||||||
cd "$(dirname ${BASH_SOURCE[0]})"
|
|
||||||
OUTPUT_DIR="$PWD/output"
|
|
||||||
. build_common
|
|
||||||
cd .. # root project dir
|
|
||||||
|
|
||||||
mkdir -p "$OUTPUT_DIR"
|
|
||||||
cp "$WORK_DIR/build-server/server/scrcpy-server" "$OUTPUT_DIR/scrcpy-server-$VERSION"
|
|
||||||
echo "Generated '$OUTPUT_DIR/scrcpy-server-$VERSION'"
|
|
@ -1,24 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# To customize the version name:
|
|
||||||
# VERSION=myversion ./release.sh
|
|
||||||
set -e
|
|
||||||
|
|
||||||
cd "$(dirname ${BASH_SOURCE[0]})"
|
|
||||||
rm -rf output
|
|
||||||
|
|
||||||
./test_server.sh
|
|
||||||
./test_client.sh
|
|
||||||
|
|
||||||
./build_server.sh
|
|
||||||
./build_windows.sh 32
|
|
||||||
./build_windows.sh 64
|
|
||||||
./build_linux.sh x86_64
|
|
||||||
|
|
||||||
./package_server.sh
|
|
||||||
./package_client.sh win32 zip
|
|
||||||
./package_client.sh win64 zip
|
|
||||||
./package_client.sh linux-x86_64 tar.gz
|
|
||||||
|
|
||||||
./generate_checksums.sh
|
|
||||||
|
|
||||||
echo "Release generated in $PWD/output"
|
|
@ -1,12 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -ex
|
|
||||||
cd "$(dirname ${BASH_SOURCE[0]})"
|
|
||||||
. build_common
|
|
||||||
cd .. # root project dir
|
|
||||||
|
|
||||||
TEST_BUILD_DIR="$WORK_DIR/build-test"
|
|
||||||
|
|
||||||
rm -rf "$TEST_BUILD_DIR"
|
|
||||||
meson setup "$TEST_BUILD_DIR" -Dcompile_server=false \
|
|
||||||
-Db_sanitize=address,undefined
|
|
||||||
ninja -C "$TEST_BUILD_DIR" test
|
|
@ -1,9 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -ex
|
|
||||||
cd "$(dirname ${BASH_SOURCE[0]})"
|
|
||||||
. build_common
|
|
||||||
cd .. # root project dir
|
|
||||||
|
|
||||||
GRADLE="${GRADLE:-./gradlew}"
|
|
||||||
|
|
||||||
"$GRADLE" -p server check
|
|
@ -7,8 +7,8 @@ android {
|
|||||||
applicationId "com.genymobile.scrcpy"
|
applicationId "com.genymobile.scrcpy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 35
|
targetSdkVersion 35
|
||||||
versionCode 30100
|
versionCode 20700
|
||||||
versionName "3.1"
|
versionName "2.7"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
@ -12,11 +12,10 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
SCRCPY_DEBUG=false
|
SCRCPY_DEBUG=false
|
||||||
SCRCPY_VERSION_NAME=3.1
|
SCRCPY_VERSION_NAME=2.7
|
||||||
|
|
||||||
PLATFORM=${ANDROID_PLATFORM:-35}
|
PLATFORM=${ANDROID_PLATFORM:-35}
|
||||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0}
|
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0}
|
||||||
PLATFORM_TOOLS="$ANDROID_HOME/platforms/android-$PLATFORM"
|
|
||||||
BUILD_TOOLS_DIR="$ANDROID_HOME/build-tools/$BUILD_TOOLS"
|
BUILD_TOOLS_DIR="$ANDROID_HOME/build-tools/$BUILD_TOOLS"
|
||||||
|
|
||||||
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
|
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
|
||||||
@ -24,8 +23,7 @@ CLASSES_DIR="$BUILD_DIR/classes"
|
|||||||
GEN_DIR="$BUILD_DIR/gen"
|
GEN_DIR="$BUILD_DIR/gen"
|
||||||
SERVER_DIR=$(dirname "$0")
|
SERVER_DIR=$(dirname "$0")
|
||||||
SERVER_BINARY=scrcpy-server
|
SERVER_BINARY=scrcpy-server
|
||||||
ANDROID_JAR="$PLATFORM_TOOLS/android.jar"
|
ANDROID_JAR="$ANDROID_HOME/platforms/android-$PLATFORM/android.jar"
|
||||||
ANDROID_AIDL="$PLATFORM_TOOLS/framework.aidl"
|
|
||||||
LAMBDA_JAR="$BUILD_TOOLS_DIR/core-lambda-stubs.jar"
|
LAMBDA_JAR="$BUILD_TOOLS_DIR/core-lambda-stubs.jar"
|
||||||
|
|
||||||
echo "Platform: android-$PLATFORM"
|
echo "Platform: android-$PLATFORM"
|
||||||
@ -51,20 +49,12 @@ cd "$SERVER_DIR/src/main/aidl"
|
|||||||
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. \
|
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. \
|
||||||
android/content/IOnPrimaryClipChangedListener.aidl
|
android/content/IOnPrimaryClipChangedListener.aidl
|
||||||
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. android/view/IDisplayFoldListener.aidl
|
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. android/view/IDisplayFoldListener.aidl
|
||||||
"$BUILD_TOOLS_DIR/aidl" -o"$GEN_DIR" -I. -p "$ANDROID_AIDL" \
|
|
||||||
android/view/IDisplayWindowListener.aidl
|
|
||||||
|
|
||||||
# Fake sources to expose hidden Android types to the project
|
|
||||||
FAKE_SRC=( \
|
|
||||||
android/content/*java \
|
|
||||||
)
|
|
||||||
|
|
||||||
SRC=( \
|
SRC=( \
|
||||||
com/genymobile/scrcpy/*.java \
|
com/genymobile/scrcpy/*.java \
|
||||||
com/genymobile/scrcpy/audio/*.java \
|
com/genymobile/scrcpy/audio/*.java \
|
||||||
com/genymobile/scrcpy/control/*.java \
|
com/genymobile/scrcpy/control/*.java \
|
||||||
com/genymobile/scrcpy/device/*.java \
|
com/genymobile/scrcpy/device/*.java \
|
||||||
com/genymobile/scrcpy/opengl/*.java \
|
|
||||||
com/genymobile/scrcpy/util/*.java \
|
com/genymobile/scrcpy/util/*.java \
|
||||||
com/genymobile/scrcpy/video/*.java \
|
com/genymobile/scrcpy/video/*.java \
|
||||||
com/genymobile/scrcpy/wrappers/*.java \
|
com/genymobile/scrcpy/wrappers/*.java \
|
||||||
@ -78,11 +68,10 @@ done
|
|||||||
|
|
||||||
echo "Compiling java sources..."
|
echo "Compiling java sources..."
|
||||||
cd ../java
|
cd ../java
|
||||||
javac -encoding UTF-8 -bootclasspath "$ANDROID_JAR" \
|
javac -bootclasspath "$ANDROID_JAR" \
|
||||||
-cp "$LAMBDA_JAR:$GEN_DIR" \
|
-cp "$LAMBDA_JAR:$GEN_DIR" \
|
||||||
-d "$CLASSES_DIR" \
|
-d "$CLASSES_DIR" \
|
||||||
-source 1.8 -target 1.8 \
|
-source 1.8 -target 1.8 \
|
||||||
${FAKE_SRC[@]} \
|
|
||||||
${SRC[@]}
|
${SRC[@]}
|
||||||
|
|
||||||
echo "Dexing..."
|
echo "Dexing..."
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2019 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package android.view;
|
|
||||||
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface to listen for changes to display window-containers.
|
|
||||||
*
|
|
||||||
* This differs from DisplayManager's DisplayListener in a couple ways:
|
|
||||||
* - onDisplayAdded is always called after the display is actually added to the WM hierarchy.
|
|
||||||
* This corresponds to the DisplayContent and not the raw Dislay from DisplayManager.
|
|
||||||
* - onDisplayConfigurationChanged is called for all configuration changes, not just changes
|
|
||||||
* to displayinfo (eg. windowing-mode).
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
oneway interface IDisplayWindowListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a new display is added to the WM hierarchy. The existing display ids are returned
|
|
||||||
* when this listener is registered with WM via {@link #registerDisplayWindowListener}.
|
|
||||||
*/
|
|
||||||
void onDisplayAdded(int displayId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a display's window-container configuration has changed.
|
|
||||||
*/
|
|
||||||
void onDisplayConfigurationChanged(int displayId, in Configuration newConfig);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a display is removed from the hierarchy.
|
|
||||||
*/
|
|
||||||
void onDisplayRemoved(int displayId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when fixed rotation is started on a display.
|
|
||||||
*/
|
|
||||||
void onFixedRotationStarted(int displayId, int newRotation);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the previous fixed rotation on a display is finished.
|
|
||||||
*/
|
|
||||||
void onFixedRotationFinished(int displayId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the keep clear ares on a display have changed.
|
|
||||||
*/
|
|
||||||
void onKeepClearAreasChanged(int displayId, in List<Rect> restricted, in List<Rect> unrestricted);
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
package android.content;
|
|
||||||
|
|
||||||
public interface IContentProvider {
|
|
||||||
// android.content.IContentProvider is hidden, this is a fake one to expose the type to the project
|
|
||||||
}
|
|
@ -5,10 +5,6 @@ import com.genymobile.scrcpy.util.Ln;
|
|||||||
import com.genymobile.scrcpy.util.Settings;
|
import com.genymobile.scrcpy.util.Settings;
|
||||||
import com.genymobile.scrcpy.util.SettingsException;
|
import com.genymobile.scrcpy.util.SettingsException;
|
||||||
|
|
||||||
import android.os.BatteryManager;
|
|
||||||
import android.system.ErrnoException;
|
|
||||||
import android.system.Os;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
@ -20,140 +16,59 @@ import java.io.OutputStream;
|
|||||||
*/
|
*/
|
||||||
public final class CleanUp {
|
public final class CleanUp {
|
||||||
|
|
||||||
// Dynamic options
|
private static final int MSG_TYPE_MASK = 0b11;
|
||||||
private static final int PENDING_CHANGE_DISPLAY_POWER = 1 << 0;
|
private static final int MSG_TYPE_RESTORE_STAY_ON = 0;
|
||||||
private int pendingChanges;
|
private static final int MSG_TYPE_DISABLE_SHOW_TOUCHES = 1;
|
||||||
private boolean pendingRestoreDisplayPower;
|
private static final int MSG_TYPE_RESTORE_NORMAL_POWER_MODE = 2;
|
||||||
|
private static final int MSG_TYPE_POWER_OFF_SCREEN = 3;
|
||||||
|
|
||||||
private Thread thread;
|
private static final int MSG_PARAM_SHIFT = 2;
|
||||||
private boolean interrupted;
|
|
||||||
|
|
||||||
private CleanUp(Options options) {
|
private final OutputStream out;
|
||||||
thread = new Thread(() -> runCleanUp(options), "cleanup");
|
|
||||||
thread.start();
|
public CleanUp(OutputStream out) {
|
||||||
|
this.out = out;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CleanUp start(Options options) {
|
public static CleanUp configure(int displayId) throws IOException {
|
||||||
return new CleanUp(options);
|
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(displayId)};
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void interrupt() {
|
|
||||||
// Do not use thread.interrupt() because only the wait() call must be interrupted, not Command.exec()
|
|
||||||
interrupted = true;
|
|
||||||
notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void join() throws InterruptedException {
|
|
||||||
thread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void runCleanUp(Options options) {
|
|
||||||
boolean disableShowTouches = false;
|
|
||||||
if (options.getShowTouches()) {
|
|
||||||
try {
|
|
||||||
String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1");
|
|
||||||
// If "show touches" was disabled, it must be disabled back on clean up
|
|
||||||
disableShowTouches = !"1".equals(oldValue);
|
|
||||||
} catch (SettingsException e) {
|
|
||||||
Ln.e("Could not change \"show_touches\"", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int restoreStayOn = -1;
|
|
||||||
if (options.getStayAwake()) {
|
|
||||||
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
|
|
||||||
try {
|
|
||||||
String oldValue = Settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
|
|
||||||
try {
|
|
||||||
int currentStayOn = Integer.parseInt(oldValue);
|
|
||||||
// Restore only if the current value is different
|
|
||||||
if (currentStayOn != stayOn) {
|
|
||||||
restoreStayOn = currentStayOn;
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
} catch (SettingsException e) {
|
|
||||||
Ln.e("Could not change \"stay_on_while_plugged_in\"", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int restoreScreenOffTimeout = -1;
|
|
||||||
int screenOffTimeout = options.getScreenOffTimeout();
|
|
||||||
if (screenOffTimeout != -1) {
|
|
||||||
try {
|
|
||||||
String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "screen_off_timeout", String.valueOf(screenOffTimeout));
|
|
||||||
try {
|
|
||||||
int currentScreenOffTimeout = Integer.parseInt(oldValue);
|
|
||||||
// Restore only if the current value is different
|
|
||||||
if (currentScreenOffTimeout != screenOffTimeout) {
|
|
||||||
restoreScreenOffTimeout = currentScreenOffTimeout;
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
} catch (SettingsException e) {
|
|
||||||
Ln.e("Could not change \"screen_off_timeout\"", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean powerOffScreen = options.getPowerOffScreenOnClose();
|
|
||||||
int displayId = options.getDisplayId();
|
|
||||||
|
|
||||||
try {
|
|
||||||
run(displayId, restoreStayOn, disableShowTouches, powerOffScreen, restoreScreenOffTimeout);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Ln.e("Clean up I/O exception", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void run(int displayId, int restoreStayOn, boolean disableShowTouches, boolean powerOffScreen, int restoreScreenOffTimeout)
|
|
||||||
throws IOException {
|
|
||||||
String[] cmd = {
|
|
||||||
"app_process",
|
|
||||||
"/",
|
|
||||||
CleanUp.class.getName(),
|
|
||||||
String.valueOf(displayId),
|
|
||||||
String.valueOf(restoreStayOn),
|
|
||||||
String.valueOf(disableShowTouches),
|
|
||||||
String.valueOf(powerOffScreen),
|
|
||||||
String.valueOf(restoreScreenOffTimeout),
|
|
||||||
};
|
|
||||||
|
|
||||||
ProcessBuilder builder = new ProcessBuilder(cmd);
|
ProcessBuilder builder = new ProcessBuilder(cmd);
|
||||||
builder.environment().put("CLASSPATH", Server.SERVER_PATH);
|
builder.environment().put("CLASSPATH", Server.SERVER_PATH);
|
||||||
Process process = builder.start();
|
Process process = builder.start();
|
||||||
OutputStream out = process.getOutputStream();
|
return new CleanUp(process.getOutputStream());
|
||||||
|
}
|
||||||
|
|
||||||
while (true) {
|
private boolean sendMessage(int type, int param) {
|
||||||
int localPendingChanges;
|
assert (type & ~MSG_TYPE_MASK) == 0;
|
||||||
boolean localPendingRestoreDisplayPower;
|
int msg = type | param << MSG_PARAM_SHIFT;
|
||||||
synchronized (this) {
|
|
||||||
while (!interrupted && pendingChanges == 0) {
|
|
||||||
try {
|
try {
|
||||||
wait();
|
out.write(msg);
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new AssertionError("Clean up thread MUST NOT be interrupted");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (interrupted) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
localPendingChanges = pendingChanges;
|
|
||||||
localPendingRestoreDisplayPower = pendingRestoreDisplayPower;
|
|
||||||
pendingChanges = 0;
|
|
||||||
}
|
|
||||||
if ((localPendingChanges & PENDING_CHANGE_DISPLAY_POWER) != 0) {
|
|
||||||
out.write(localPendingRestoreDisplayPower ? 1 : 0);
|
|
||||||
out.flush();
|
out.flush();
|
||||||
}
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Ln.w("Could not configure cleanup (type=" + type + ", param=" + param + ")", e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void setRestoreDisplayPower(boolean restoreDisplayPower) {
|
public boolean setRestoreStayOn(int restoreValue) {
|
||||||
pendingRestoreDisplayPower = restoreDisplayPower;
|
// Restore the value (between 0 and 7), -1 to not restore
|
||||||
pendingChanges |= PENDING_CHANGE_DISPLAY_POWER;
|
// <https://developer.android.com/reference/android/provider/Settings.Global#STAY_ON_WHILE_PLUGGED_IN>
|
||||||
notify();
|
assert restoreValue >= -1 && restoreValue <= 7;
|
||||||
|
return sendMessage(MSG_TYPE_RESTORE_STAY_ON, restoreValue & 0b1111);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setDisableShowTouches(boolean disableOnExit) {
|
||||||
|
return sendMessage(MSG_TYPE_DISABLE_SHOW_TOUCHES, disableOnExit ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setRestoreNormalPowerMode(boolean restoreOnExit) {
|
||||||
|
return sendMessage(MSG_TYPE_RESTORE_NORMAL_POWER_MODE, restoreOnExit ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setPowerOffScreen(boolean powerOffScreenOnExit) {
|
||||||
|
return sendMessage(MSG_TYPE_POWER_OFF_SCREEN, powerOffScreenOnExit ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void unlinkSelf() {
|
public static void unlinkSelf() {
|
||||||
@ -165,30 +80,38 @@ public final class CleanUp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String... args) {
|
public static void main(String... args) {
|
||||||
try {
|
|
||||||
// Start a new session to avoid being terminated along with the server process on some devices
|
|
||||||
Os.setsid();
|
|
||||||
} catch (ErrnoException e) {
|
|
||||||
Ln.e("setsid() failed", e);
|
|
||||||
}
|
|
||||||
unlinkSelf();
|
unlinkSelf();
|
||||||
|
|
||||||
int displayId = Integer.parseInt(args[0]);
|
int displayId = Integer.parseInt(args[0]);
|
||||||
int restoreStayOn = Integer.parseInt(args[1]);
|
|
||||||
boolean disableShowTouches = Boolean.parseBoolean(args[2]);
|
|
||||||
boolean powerOffScreen = Boolean.parseBoolean(args[3]);
|
|
||||||
int restoreScreenOffTimeout = Integer.parseInt(args[4]);
|
|
||||||
|
|
||||||
// Dynamic option
|
int restoreStayOn = -1;
|
||||||
boolean restoreDisplayPower = false;
|
boolean disableShowTouches = false;
|
||||||
|
boolean restoreNormalPowerMode = false;
|
||||||
|
boolean powerOffScreen = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Wait for the server to die
|
// Wait for the server to die
|
||||||
int msg;
|
int msg;
|
||||||
while ((msg = System.in.read()) != -1) {
|
while ((msg = System.in.read()) != -1) {
|
||||||
// Only restore display power
|
int type = msg & MSG_TYPE_MASK;
|
||||||
assert msg == 0 || msg == 1;
|
int param = msg >> MSG_PARAM_SHIFT;
|
||||||
restoreDisplayPower = msg != 0;
|
switch (type) {
|
||||||
|
case MSG_TYPE_RESTORE_STAY_ON:
|
||||||
|
restoreStayOn = param > 7 ? -1 : param;
|
||||||
|
break;
|
||||||
|
case MSG_TYPE_DISABLE_SHOW_TOUCHES:
|
||||||
|
disableShowTouches = param != 0;
|
||||||
|
break;
|
||||||
|
case MSG_TYPE_RESTORE_NORMAL_POWER_MODE:
|
||||||
|
restoreNormalPowerMode = param != 0;
|
||||||
|
break;
|
||||||
|
case MSG_TYPE_POWER_OFF_SCREEN:
|
||||||
|
powerOffScreen = param != 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Ln.w("Unexpected msg type: " + type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Expected when the server is dead
|
// Expected when the server is dead
|
||||||
@ -214,24 +137,15 @@ public final class CleanUp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (restoreScreenOffTimeout != -1) {
|
if (Device.isScreenOn()) {
|
||||||
Ln.i("Restoring \"screen off timeout\"");
|
|
||||||
try {
|
|
||||||
Settings.putValue(Settings.TABLE_SYSTEM, "screen_off_timeout", String.valueOf(restoreScreenOffTimeout));
|
|
||||||
} catch (SettingsException e) {
|
|
||||||
Ln.e("Could not restore \"screen_off_timeout\"", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
||||||
|
if (displayId != Device.DISPLAY_ID_NONE) {
|
||||||
Ln.i("Power off screen");
|
Ln.i("Power off screen");
|
||||||
Device.powerOffScreen(targetDisplayId);
|
Device.powerOffScreen(displayId);
|
||||||
} else if (restoreDisplayPower) {
|
}
|
||||||
Ln.i("Restoring display power");
|
} else if (restoreNormalPowerMode) {
|
||||||
Device.setDisplayPower(targetDisplayId, true);
|
Ln.i("Restoring normal power mode");
|
||||||
|
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.AttributionSource;
|
import android.content.AttributionSource;
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.ContextWrapper;
|
import android.content.ContextWrapper;
|
||||||
import android.content.IContentProvider;
|
|
||||||
import android.os.Binder;
|
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
|
|
||||||
public final class FakeContext extends ContextWrapper {
|
public final class FakeContext extends ContextWrapper {
|
||||||
@ -22,38 +17,6 @@ public final class FakeContext extends ContextWrapper {
|
|||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ContentResolver contentResolver = new ContentResolver(this) {
|
|
||||||
@SuppressWarnings({"unused", "ProtectedMemberInFinalClass"})
|
|
||||||
// @Override (but super-class method not visible)
|
|
||||||
protected IContentProvider acquireProvider(Context c, String name) {
|
|
||||||
return ServiceManager.getActivityManager().getContentProviderExternal(name, new Binder());
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
// @Override (but super-class method not visible)
|
|
||||||
public boolean releaseProvider(IContentProvider icp) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"unused", "ProtectedMemberInFinalClass"})
|
|
||||||
// @Override (but super-class method not visible)
|
|
||||||
protected IContentProvider acquireUnstableProvider(Context c, String name) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
// @Override (but super-class method not visible)
|
|
||||||
public boolean releaseUnstableProvider(IContentProvider icp) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
// @Override (but super-class method not visible)
|
|
||||||
public void unstableProviderDied(IContentProvider icp) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private FakeContext() {
|
private FakeContext() {
|
||||||
super(Workarounds.getSystemContext());
|
super(Workarounds.getSystemContext());
|
||||||
}
|
}
|
||||||
@ -86,9 +49,4 @@ public final class FakeContext extends ContextWrapper {
|
|||||||
public Context getApplicationContext() {
|
public Context getApplicationContext() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ContentResolver getContentResolver() {
|
|
||||||
return contentResolver;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@ package com.genymobile.scrcpy;
|
|||||||
|
|
||||||
import com.genymobile.scrcpy.audio.AudioCodec;
|
import com.genymobile.scrcpy.audio.AudioCodec;
|
||||||
import com.genymobile.scrcpy.audio.AudioSource;
|
import com.genymobile.scrcpy.audio.AudioSource;
|
||||||
import com.genymobile.scrcpy.device.Device;
|
|
||||||
import com.genymobile.scrcpy.device.NewDisplay;
|
import com.genymobile.scrcpy.device.NewDisplay;
|
||||||
import com.genymobile.scrcpy.device.Orientation;
|
|
||||||
import com.genymobile.scrcpy.device.Size;
|
import com.genymobile.scrcpy.device.Size;
|
||||||
import com.genymobile.scrcpy.util.CodecOption;
|
import com.genymobile.scrcpy.util.CodecOption;
|
||||||
import com.genymobile.scrcpy.util.Ln;
|
import com.genymobile.scrcpy.util.Ln;
|
||||||
@ -14,7 +12,6 @@ import com.genymobile.scrcpy.video.VideoCodec;
|
|||||||
import com.genymobile.scrcpy.video.VideoSource;
|
import com.genymobile.scrcpy.video.VideoSource;
|
||||||
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -34,7 +31,7 @@ public class Options {
|
|||||||
private int videoBitRate = 8000000;
|
private int videoBitRate = 8000000;
|
||||||
private int audioBitRate = 128000;
|
private int audioBitRate = 128000;
|
||||||
private float maxFps;
|
private float maxFps;
|
||||||
private float angle;
|
private int lockVideoOrientation = -1;
|
||||||
private boolean tunnelForward;
|
private boolean tunnelForward;
|
||||||
private Rect crop;
|
private Rect crop;
|
||||||
private boolean control = true;
|
private boolean control = true;
|
||||||
@ -47,7 +44,6 @@ public class Options {
|
|||||||
private boolean cameraHighSpeed;
|
private boolean cameraHighSpeed;
|
||||||
private boolean showTouches;
|
private boolean showTouches;
|
||||||
private boolean stayAwake;
|
private boolean stayAwake;
|
||||||
private int screenOffTimeout = -1;
|
|
||||||
private List<CodecOption> videoCodecOptions;
|
private List<CodecOption> videoCodecOptions;
|
||||||
private List<CodecOption> audioCodecOptions;
|
private List<CodecOption> audioCodecOptions;
|
||||||
|
|
||||||
@ -60,11 +56,6 @@ public class Options {
|
|||||||
private boolean powerOn = true;
|
private boolean powerOn = true;
|
||||||
|
|
||||||
private NewDisplay newDisplay;
|
private NewDisplay newDisplay;
|
||||||
private boolean vdDestroyContent = true;
|
|
||||||
private boolean vdSystemDecorations = true;
|
|
||||||
|
|
||||||
private Orientation.Lock captureOrientationLock = Orientation.Lock.Unlocked;
|
|
||||||
private Orientation captureOrientation = Orientation.Orient0;
|
|
||||||
|
|
||||||
private boolean listEncoders;
|
private boolean listEncoders;
|
||||||
private boolean listDisplays;
|
private boolean listDisplays;
|
||||||
@ -130,8 +121,8 @@ public class Options {
|
|||||||
return maxFps;
|
return maxFps;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getAngle() {
|
public int getLockVideoOrientation() {
|
||||||
return angle;
|
return lockVideoOrientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTunnelForward() {
|
public boolean isTunnelForward() {
|
||||||
@ -182,10 +173,6 @@ public class Options {
|
|||||||
return stayAwake;
|
return stayAwake;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getScreenOffTimeout() {
|
|
||||||
return screenOffTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<CodecOption> getVideoCodecOptions() {
|
public List<CodecOption> getVideoCodecOptions() {
|
||||||
return videoCodecOptions;
|
return videoCodecOptions;
|
||||||
}
|
}
|
||||||
@ -226,22 +213,6 @@ public class Options {
|
|||||||
return newDisplay;
|
return newDisplay;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Orientation getCaptureOrientation() {
|
|
||||||
return captureOrientation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Orientation.Lock getCaptureOrientationLock() {
|
|
||||||
return captureOrientationLock;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getVDDestroyContent() {
|
|
||||||
return vdDestroyContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getVDSystemDecorations() {
|
|
||||||
return vdSystemDecorations;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getList() {
|
public boolean getList() {
|
||||||
return listEncoders || listDisplays || listCameras || listCameraSizes || listApps;
|
return listEncoders || listDisplays || listCameras || listCameraSizes || listApps;
|
||||||
}
|
}
|
||||||
@ -364,8 +335,8 @@ public class Options {
|
|||||||
case "max_fps":
|
case "max_fps":
|
||||||
options.maxFps = parseFloat("max_fps", value);
|
options.maxFps = parseFloat("max_fps", value);
|
||||||
break;
|
break;
|
||||||
case "angle":
|
case "lock_video_orientation":
|
||||||
options.angle = parseFloat("angle", value);
|
options.lockVideoOrientation = Integer.parseInt(value);
|
||||||
break;
|
break;
|
||||||
case "tunnel_forward":
|
case "tunnel_forward":
|
||||||
options.tunnelForward = Boolean.parseBoolean(value);
|
options.tunnelForward = Boolean.parseBoolean(value);
|
||||||
@ -387,12 +358,6 @@ public class Options {
|
|||||||
case "stay_awake":
|
case "stay_awake":
|
||||||
options.stayAwake = Boolean.parseBoolean(value);
|
options.stayAwake = Boolean.parseBoolean(value);
|
||||||
break;
|
break;
|
||||||
case "screen_off_timeout":
|
|
||||||
options.screenOffTimeout = Integer.parseInt(value);
|
|
||||||
if (options.screenOffTimeout < -1) {
|
|
||||||
throw new IllegalArgumentException("Invalid screen off timeout: " + options.screenOffTimeout);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "video_codec_options":
|
case "video_codec_options":
|
||||||
options.videoCodecOptions = CodecOption.parse(value);
|
options.videoCodecOptions = CodecOption.parse(value);
|
||||||
break;
|
break;
|
||||||
@ -471,17 +436,6 @@ public class Options {
|
|||||||
case "new_display":
|
case "new_display":
|
||||||
options.newDisplay = parseNewDisplay(value);
|
options.newDisplay = parseNewDisplay(value);
|
||||||
break;
|
break;
|
||||||
case "vd_destroy_content":
|
|
||||||
options.vdDestroyContent = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "vd_system_decorations":
|
|
||||||
options.vdSystemDecorations = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "capture_orientation":
|
|
||||||
Pair<Orientation.Lock, Orientation> pair = parseCaptureOrientation(value);
|
|
||||||
options.captureOrientationLock = pair.first;
|
|
||||||
options.captureOrientation = pair.second;
|
|
||||||
break;
|
|
||||||
case "send_device_meta":
|
case "send_device_meta":
|
||||||
options.sendDeviceMeta = Boolean.parseBoolean(value);
|
options.sendDeviceMeta = Boolean.parseBoolean(value);
|
||||||
break;
|
break;
|
||||||
@ -509,11 +463,6 @@ public class Options {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.newDisplay != null) {
|
|
||||||
assert options.displayId == 0 : "Must not set both displayId and newDisplay";
|
|
||||||
options.displayId = Device.DISPLAY_ID_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -605,25 +554,4 @@ public class Options {
|
|||||||
|
|
||||||
return new NewDisplay(size, dpi);
|
return new NewDisplay(size, dpi);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Pair<Orientation.Lock, Orientation> parseCaptureOrientation(String value) {
|
|
||||||
if (value.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("Empty capture orientation string");
|
|
||||||
}
|
|
||||||
|
|
||||||
Orientation.Lock lock;
|
|
||||||
if (value.charAt(0) == '@') {
|
|
||||||
// Consume '@'
|
|
||||||
value = value.substring(1);
|
|
||||||
if (value.isEmpty()) {
|
|
||||||
// Only '@': lock to the initial orientation (orientation is unused)
|
|
||||||
return Pair.create(Orientation.Lock.LockedInitial, Orientation.Orient0);
|
|
||||||
}
|
|
||||||
lock = Orientation.Lock.LockedValue;
|
|
||||||
} else {
|
|
||||||
lock = Orientation.Lock.Unlocked;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Pair.create(lock, Orientation.getByName(value));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,10 @@ import com.genymobile.scrcpy.device.DesktopConnection;
|
|||||||
import com.genymobile.scrcpy.device.Device;
|
import com.genymobile.scrcpy.device.Device;
|
||||||
import com.genymobile.scrcpy.device.NewDisplay;
|
import com.genymobile.scrcpy.device.NewDisplay;
|
||||||
import com.genymobile.scrcpy.device.Streamer;
|
import com.genymobile.scrcpy.device.Streamer;
|
||||||
import com.genymobile.scrcpy.opengl.OpenGLRunner;
|
|
||||||
import com.genymobile.scrcpy.util.Ln;
|
import com.genymobile.scrcpy.util.Ln;
|
||||||
import com.genymobile.scrcpy.util.LogUtils;
|
import com.genymobile.scrcpy.util.LogUtils;
|
||||||
|
import com.genymobile.scrcpy.util.Settings;
|
||||||
|
import com.genymobile.scrcpy.util.SettingsException;
|
||||||
import com.genymobile.scrcpy.video.CameraCapture;
|
import com.genymobile.scrcpy.video.CameraCapture;
|
||||||
import com.genymobile.scrcpy.video.NewDisplayCapture;
|
import com.genymobile.scrcpy.video.NewDisplayCapture;
|
||||||
import com.genymobile.scrcpy.video.ScreenCapture;
|
import com.genymobile.scrcpy.video.ScreenCapture;
|
||||||
@ -24,6 +25,7 @@ import com.genymobile.scrcpy.video.SurfaceCapture;
|
|||||||
import com.genymobile.scrcpy.video.SurfaceEncoder;
|
import com.genymobile.scrcpy.video.SurfaceEncoder;
|
||||||
import com.genymobile.scrcpy.video.VideoSource;
|
import com.genymobile.scrcpy.video.VideoSource;
|
||||||
|
|
||||||
|
import android.os.BatteryManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -74,6 +76,51 @@ public final class Server {
|
|||||||
// not instantiable
|
// not instantiable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void initAndCleanUp(Options options, CleanUp cleanUp) {
|
||||||
|
// This method is called from its own thread, so it may only configure cleanup actions which are NOT dynamic (i.e. they are configured once
|
||||||
|
// and for all, they cannot be changed from another thread)
|
||||||
|
|
||||||
|
if (options.getShowTouches()) {
|
||||||
|
try {
|
||||||
|
String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1");
|
||||||
|
// If "show touches" was disabled, it must be disabled back on clean up
|
||||||
|
if (!"1".equals(oldValue)) {
|
||||||
|
if (!cleanUp.setDisableShowTouches(true)) {
|
||||||
|
Ln.e("Could not disable show touch on exit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SettingsException e) {
|
||||||
|
Ln.e("Could not change \"show_touches\"", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.getStayAwake()) {
|
||||||
|
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
|
||||||
|
try {
|
||||||
|
String oldValue = Settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
|
||||||
|
try {
|
||||||
|
int restoreStayOn = Integer.parseInt(oldValue);
|
||||||
|
if (restoreStayOn != stayOn) {
|
||||||
|
// Restore only if the current value is different
|
||||||
|
if (!cleanUp.setRestoreStayOn(restoreStayOn)) {
|
||||||
|
Ln.e("Could not restore stay on on exit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
} catch (SettingsException e) {
|
||||||
|
Ln.e("Could not change \"stay_on_while_plugged_in\"", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.getPowerOffScreenOnClose()) {
|
||||||
|
if (!cleanUp.setPowerOffScreen(true)) {
|
||||||
|
Ln.e("Could not power off screen on exit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void scrcpy(Options options) throws IOException, ConfigurationException {
|
private static void scrcpy(Options options) throws IOException, ConfigurationException {
|
||||||
if (Build.VERSION.SDK_INT < AndroidVersions.API_31_ANDROID_12 && options.getVideoSource() == VideoSource.CAMERA) {
|
if (Build.VERSION.SDK_INT < AndroidVersions.API_31_ANDROID_12 && options.getVideoSource() == VideoSource.CAMERA) {
|
||||||
Ln.e("Camera mirroring is not supported before Android 12");
|
Ln.e("Camera mirroring is not supported before Android 12");
|
||||||
@ -86,9 +133,14 @@ public final class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CleanUp cleanUp = null;
|
CleanUp cleanUp = null;
|
||||||
|
Thread initThread = null;
|
||||||
|
|
||||||
|
NewDisplay newDisplay = options.getNewDisplay();
|
||||||
|
int displayId = newDisplay == null ? options.getDisplayId() : Device.DISPLAY_ID_NONE;
|
||||||
|
|
||||||
if (options.getCleanup()) {
|
if (options.getCleanup()) {
|
||||||
cleanUp = CleanUp.start(options);
|
cleanUp = CleanUp.configure(displayId);
|
||||||
|
initThread = startInitThread(options, cleanUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
int scid = options.getScid();
|
int scid = options.getScid();
|
||||||
@ -112,7 +164,7 @@ public final class Server {
|
|||||||
|
|
||||||
if (control) {
|
if (control) {
|
||||||
ControlChannel controlChannel = connection.getControlChannel();
|
ControlChannel controlChannel = connection.getControlChannel();
|
||||||
controller = new Controller(controlChannel, cleanUp, options);
|
controller = new Controller(displayId, controlChannel, cleanUp, options.getClipboardAutosync(), options.getPowerOn());
|
||||||
asyncProcessors.add(controller);
|
asyncProcessors.add(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +183,8 @@ public final class Server {
|
|||||||
if (audioCodec == AudioCodec.RAW) {
|
if (audioCodec == AudioCodec.RAW) {
|
||||||
audioRecorder = new AudioRawRecorder(audioCapture, audioStreamer);
|
audioRecorder = new AudioRawRecorder(audioCapture, audioStreamer);
|
||||||
} else {
|
} else {
|
||||||
audioRecorder = new AudioEncoder(audioCapture, audioStreamer, options);
|
audioRecorder = new AudioEncoder(audioCapture, audioStreamer, options.getAudioBitRate(), options.getAudioCodecOptions(),
|
||||||
|
options.getAudioEncoder());
|
||||||
}
|
}
|
||||||
asyncProcessors.add(audioRecorder);
|
asyncProcessors.add(audioRecorder);
|
||||||
}
|
}
|
||||||
@ -141,22 +194,20 @@ public final class Server {
|
|||||||
options.getSendFrameMeta());
|
options.getSendFrameMeta());
|
||||||
SurfaceCapture surfaceCapture;
|
SurfaceCapture surfaceCapture;
|
||||||
if (options.getVideoSource() == VideoSource.DISPLAY) {
|
if (options.getVideoSource() == VideoSource.DISPLAY) {
|
||||||
NewDisplay newDisplay = options.getNewDisplay();
|
|
||||||
if (newDisplay != null) {
|
if (newDisplay != null) {
|
||||||
surfaceCapture = new NewDisplayCapture(controller, options);
|
surfaceCapture = new NewDisplayCapture(controller, newDisplay, options.getMaxSize());
|
||||||
} else {
|
} else {
|
||||||
assert options.getDisplayId() != Device.DISPLAY_ID_NONE;
|
assert displayId != Device.DISPLAY_ID_NONE;
|
||||||
surfaceCapture = new ScreenCapture(controller, options);
|
surfaceCapture = new ScreenCapture(controller, displayId, options.getMaxSize(), options.getCrop(),
|
||||||
|
options.getLockVideoOrientation());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
surfaceCapture = new CameraCapture(options);
|
surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraFacing(), options.getCameraSize(),
|
||||||
|
options.getMaxSize(), options.getCameraAspectRatio(), options.getCameraFps(), options.getCameraHighSpeed());
|
||||||
}
|
}
|
||||||
SurfaceEncoder surfaceEncoder = new SurfaceEncoder(surfaceCapture, videoStreamer, options);
|
SurfaceEncoder surfaceEncoder = new SurfaceEncoder(surfaceCapture, videoStreamer, options.getVideoBitRate(), options.getMaxFps(),
|
||||||
|
options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError());
|
||||||
asyncProcessors.add(surfaceEncoder);
|
asyncProcessors.add(surfaceEncoder);
|
||||||
|
|
||||||
if (controller != null) {
|
|
||||||
controller.setSurfaceCapture(surfaceCapture);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Completion completion = new Completion(asyncProcessors.size());
|
Completion completion = new Completion(asyncProcessors.size());
|
||||||
@ -168,25 +219,22 @@ public final class Server {
|
|||||||
|
|
||||||
completion.await();
|
completion.await();
|
||||||
} finally {
|
} finally {
|
||||||
if (cleanUp != null) {
|
if (initThread != null) {
|
||||||
cleanUp.interrupt();
|
initThread.interrupt();
|
||||||
}
|
}
|
||||||
for (AsyncProcessor asyncProcessor : asyncProcessors) {
|
for (AsyncProcessor asyncProcessor : asyncProcessors) {
|
||||||
asyncProcessor.stop();
|
asyncProcessor.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenGLRunner.quit(); // quit the OpenGL thread, if any
|
|
||||||
|
|
||||||
connection.shutdown();
|
connection.shutdown();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (cleanUp != null) {
|
if (initThread != null) {
|
||||||
cleanUp.join();
|
initThread.join();
|
||||||
}
|
}
|
||||||
for (AsyncProcessor asyncProcessor : asyncProcessors) {
|
for (AsyncProcessor asyncProcessor : asyncProcessors) {
|
||||||
asyncProcessor.join();
|
asyncProcessor.join();
|
||||||
}
|
}
|
||||||
OpenGLRunner.join();
|
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
@ -195,6 +243,12 @@ public final class Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Thread startInitThread(final Options options, final CleanUp cleanUp) {
|
||||||
|
Thread thread = new Thread(() -> initAndCleanUp(options, cleanUp), "init-cleanup");
|
||||||
|
thread.start();
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String... args) {
|
public static void main(String... args) {
|
||||||
int status = 0;
|
int status = 0;
|
||||||
try {
|
try {
|
||||||
|
@ -2,7 +2,6 @@ package com.genymobile.scrcpy.audio;
|
|||||||
|
|
||||||
import com.genymobile.scrcpy.AndroidVersions;
|
import com.genymobile.scrcpy.AndroidVersions;
|
||||||
import com.genymobile.scrcpy.AsyncProcessor;
|
import com.genymobile.scrcpy.AsyncProcessor;
|
||||||
import com.genymobile.scrcpy.Options;
|
|
||||||
import com.genymobile.scrcpy.device.ConfigurationException;
|
import com.genymobile.scrcpy.device.ConfigurationException;
|
||||||
import com.genymobile.scrcpy.device.Streamer;
|
import com.genymobile.scrcpy.device.Streamer;
|
||||||
import com.genymobile.scrcpy.util.Codec;
|
import com.genymobile.scrcpy.util.Codec;
|
||||||
@ -68,12 +67,12 @@ public final class AudioEncoder implements AsyncProcessor {
|
|||||||
|
|
||||||
private boolean ended;
|
private boolean ended;
|
||||||
|
|
||||||
public AudioEncoder(AudioCapture capture, Streamer streamer, Options options) {
|
public AudioEncoder(AudioCapture capture, Streamer streamer, int bitRate, List<CodecOption> codecOptions, String encoderName) {
|
||||||
this.capture = capture;
|
this.capture = capture;
|
||||||
this.streamer = streamer;
|
this.streamer = streamer;
|
||||||
this.bitRate = options.getAudioBitRate();
|
this.bitRate = bitRate;
|
||||||
this.codecOptions = options.getAudioCodecOptions();
|
this.codecOptions = codecOptions;
|
||||||
this.encoderName = options.getAudioEncoder();
|
this.encoderName = encoderName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MediaFormat createFormat(String mimeType, int bitRate, List<CodecOption> codecOptions) {
|
private static MediaFormat createFormat(String mimeType, int bitRate, List<CodecOption> codecOptions) {
|
||||||
|
@ -17,14 +17,13 @@ public final class ControlMessage {
|
|||||||
public static final int TYPE_COLLAPSE_PANELS = 7;
|
public static final int TYPE_COLLAPSE_PANELS = 7;
|
||||||
public static final int TYPE_GET_CLIPBOARD = 8;
|
public static final int TYPE_GET_CLIPBOARD = 8;
|
||||||
public static final int TYPE_SET_CLIPBOARD = 9;
|
public static final int TYPE_SET_CLIPBOARD = 9;
|
||||||
public static final int TYPE_SET_DISPLAY_POWER = 10;
|
public static final int TYPE_SET_SCREEN_POWER_MODE = 10;
|
||||||
public static final int TYPE_ROTATE_DEVICE = 11;
|
public static final int TYPE_ROTATE_DEVICE = 11;
|
||||||
public static final int TYPE_UHID_CREATE = 12;
|
public static final int TYPE_UHID_CREATE = 12;
|
||||||
public static final int TYPE_UHID_INPUT = 13;
|
public static final int TYPE_UHID_INPUT = 13;
|
||||||
public static final int TYPE_UHID_DESTROY = 14;
|
public static final int TYPE_UHID_DESTROY = 14;
|
||||||
public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 15;
|
public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 15;
|
||||||
public static final int TYPE_START_APP = 16;
|
public static final int TYPE_START_APP = 16;
|
||||||
public static final int TYPE_RESET_VIDEO = 17;
|
|
||||||
|
|
||||||
public static final long SEQUENCE_INVALID = 0;
|
public static final long SEQUENCE_INVALID = 0;
|
||||||
|
|
||||||
@ -35,7 +34,7 @@ public final class ControlMessage {
|
|||||||
private int type;
|
private int type;
|
||||||
private String text;
|
private String text;
|
||||||
private int metaState; // KeyEvent.META_*
|
private int metaState; // KeyEvent.META_*
|
||||||
private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_*
|
private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_* or POWER_MODE_*
|
||||||
private int keycode; // KeyEvent.KEYCODE_*
|
private int keycode; // KeyEvent.KEYCODE_*
|
||||||
private int actionButton; // MotionEvent.BUTTON_*
|
private int actionButton; // MotionEvent.BUTTON_*
|
||||||
private int buttons; // MotionEvent.BUTTON_*
|
private int buttons; // MotionEvent.BUTTON_*
|
||||||
@ -50,9 +49,6 @@ public final class ControlMessage {
|
|||||||
private long sequence;
|
private long sequence;
|
||||||
private int id;
|
private int id;
|
||||||
private byte[] data;
|
private byte[] data;
|
||||||
private boolean on;
|
|
||||||
private int vendorId;
|
|
||||||
private int productId;
|
|
||||||
|
|
||||||
private ControlMessage() {
|
private ControlMessage() {
|
||||||
}
|
}
|
||||||
@ -120,10 +116,13 @@ public final class ControlMessage {
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ControlMessage createSetDisplayPower(boolean on) {
|
/**
|
||||||
|
* @param mode one of the {@code Device.SCREEN_POWER_MODE_*} constants
|
||||||
|
*/
|
||||||
|
public static ControlMessage createSetScreenPowerMode(int mode) {
|
||||||
ControlMessage msg = new ControlMessage();
|
ControlMessage msg = new ControlMessage();
|
||||||
msg.type = TYPE_SET_DISPLAY_POWER;
|
msg.type = TYPE_SET_SCREEN_POWER_MODE;
|
||||||
msg.on = on;
|
msg.action = mode;
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,12 +132,10 @@ public final class ControlMessage {
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ControlMessage createUhidCreate(int id, int vendorId, int productId, String name, byte[] reportDesc) {
|
public static ControlMessage createUhidCreate(int id, String name, byte[] reportDesc) {
|
||||||
ControlMessage msg = new ControlMessage();
|
ControlMessage msg = new ControlMessage();
|
||||||
msg.type = TYPE_UHID_CREATE;
|
msg.type = TYPE_UHID_CREATE;
|
||||||
msg.id = id;
|
msg.id = id;
|
||||||
msg.vendorId = vendorId;
|
|
||||||
msg.productId = productId;
|
|
||||||
msg.text = name;
|
msg.text = name;
|
||||||
msg.data = reportDesc;
|
msg.data = reportDesc;
|
||||||
return msg;
|
return msg;
|
||||||
@ -237,16 +234,4 @@ public final class ControlMessage {
|
|||||||
public byte[] getData() {
|
public byte[] getData() {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getOn() {
|
|
||||||
return on;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getVendorId() {
|
|
||||||
return vendorId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getProductId() {
|
|
||||||
return productId;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -39,14 +39,13 @@ public class ControlMessageReader {
|
|||||||
return parseGetClipboard();
|
return parseGetClipboard();
|
||||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||||
return parseSetClipboard();
|
return parseSetClipboard();
|
||||||
case ControlMessage.TYPE_SET_DISPLAY_POWER:
|
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||||
return parseSetDisplayPower();
|
return parseSetScreenPowerMode();
|
||||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
|
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
|
||||||
case ControlMessage.TYPE_COLLAPSE_PANELS:
|
case ControlMessage.TYPE_COLLAPSE_PANELS:
|
||||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||||
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||||
case ControlMessage.TYPE_RESET_VIDEO:
|
|
||||||
return ControlMessage.createEmpty(type);
|
return ControlMessage.createEmpty(type);
|
||||||
case ControlMessage.TYPE_UHID_CREATE:
|
case ControlMessage.TYPE_UHID_CREATE:
|
||||||
return parseUhidCreate();
|
return parseUhidCreate();
|
||||||
@ -135,18 +134,16 @@ public class ControlMessageReader {
|
|||||||
return ControlMessage.createSetClipboard(sequence, text, paste);
|
return ControlMessage.createSetClipboard(sequence, text, paste);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseSetDisplayPower() throws IOException {
|
private ControlMessage parseSetScreenPowerMode() throws IOException {
|
||||||
boolean on = dis.readBoolean();
|
int mode = dis.readUnsignedByte();
|
||||||
return ControlMessage.createSetDisplayPower(on);
|
return ControlMessage.createSetScreenPowerMode(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseUhidCreate() throws IOException {
|
private ControlMessage parseUhidCreate() throws IOException {
|
||||||
int id = dis.readUnsignedShort();
|
int id = dis.readUnsignedShort();
|
||||||
int vendorId = dis.readUnsignedShort();
|
|
||||||
int productId = dis.readUnsignedShort();
|
|
||||||
String name = parseString(1);
|
String name = parseString(1);
|
||||||
byte[] data = parseByteArray(2);
|
byte[] data = parseByteArray(2);
|
||||||
return ControlMessage.createUhidCreate(id, vendorId, productId, name, data);
|
return ControlMessage.createUhidCreate(id, name, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseUhidInput() throws IOException {
|
private ControlMessage parseUhidInput() throws IOException {
|
||||||
|
@ -3,15 +3,12 @@ package com.genymobile.scrcpy.control;
|
|||||||
import com.genymobile.scrcpy.AndroidVersions;
|
import com.genymobile.scrcpy.AndroidVersions;
|
||||||
import com.genymobile.scrcpy.AsyncProcessor;
|
import com.genymobile.scrcpy.AsyncProcessor;
|
||||||
import com.genymobile.scrcpy.CleanUp;
|
import com.genymobile.scrcpy.CleanUp;
|
||||||
import com.genymobile.scrcpy.Options;
|
|
||||||
import com.genymobile.scrcpy.device.Device;
|
import com.genymobile.scrcpy.device.Device;
|
||||||
import com.genymobile.scrcpy.device.DeviceApp;
|
import com.genymobile.scrcpy.device.DeviceApp;
|
||||||
import com.genymobile.scrcpy.device.Point;
|
import com.genymobile.scrcpy.device.Point;
|
||||||
import com.genymobile.scrcpy.device.Position;
|
import com.genymobile.scrcpy.device.Position;
|
||||||
import com.genymobile.scrcpy.device.Size;
|
|
||||||
import com.genymobile.scrcpy.util.Ln;
|
import com.genymobile.scrcpy.util.Ln;
|
||||||
import com.genymobile.scrcpy.util.LogUtils;
|
import com.genymobile.scrcpy.util.LogUtils;
|
||||||
import com.genymobile.scrcpy.video.SurfaceCapture;
|
|
||||||
import com.genymobile.scrcpy.video.VirtualDisplayListener;
|
import com.genymobile.scrcpy.video.VirtualDisplayListener;
|
||||||
import com.genymobile.scrcpy.wrappers.ClipboardManager;
|
import com.genymobile.scrcpy.wrappers.ClipboardManager;
|
||||||
import com.genymobile.scrcpy.wrappers.InputManager;
|
import com.genymobile.scrcpy.wrappers.InputManager;
|
||||||
@ -21,7 +18,6 @@ import android.content.IOnPrimaryClipChangedListener;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.Pair;
|
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
import android.view.KeyCharacterMap;
|
import android.view.KeyCharacterMap;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
@ -95,17 +91,14 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
private final MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[PointersState.MAX_POINTERS];
|
private final MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[PointersState.MAX_POINTERS];
|
||||||
private final MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[PointersState.MAX_POINTERS];
|
private final MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[PointersState.MAX_POINTERS];
|
||||||
|
|
||||||
private boolean keepDisplayPowerOff;
|
private boolean keepPowerModeOff;
|
||||||
|
|
||||||
// Used for resetting video encoding on RESET_VIDEO message
|
public Controller(int displayId, ControlChannel controlChannel, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) {
|
||||||
private SurfaceCapture surfaceCapture;
|
this.displayId = displayId;
|
||||||
|
|
||||||
public Controller(ControlChannel controlChannel, CleanUp cleanUp, Options options) {
|
|
||||||
this.displayId = options.getDisplayId();
|
|
||||||
this.controlChannel = controlChannel;
|
this.controlChannel = controlChannel;
|
||||||
this.cleanUp = cleanUp;
|
this.cleanUp = cleanUp;
|
||||||
this.clipboardAutosync = options.getClipboardAutosync();
|
this.clipboardAutosync = clipboardAutosync;
|
||||||
this.powerOn = options.getPowerOn();
|
this.powerOn = powerOn;
|
||||||
initPointers();
|
initPointers();
|
||||||
sender = new DeviceMessageSender(controlChannel);
|
sender = new DeviceMessageSender(controlChannel);
|
||||||
|
|
||||||
@ -150,10 +143,6 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSurfaceCapture(SurfaceCapture surfaceCapture) {
|
|
||||||
this.surfaceCapture = surfaceCapture;
|
|
||||||
}
|
|
||||||
|
|
||||||
private UhidManager getUhidManager() {
|
private UhidManager getUhidManager() {
|
||||||
if (uhidManager == null) {
|
if (uhidManager == null) {
|
||||||
uhidManager = new UhidManager(sender);
|
uhidManager = new UhidManager(sender);
|
||||||
@ -177,7 +166,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
|
|
||||||
private void control() throws IOException {
|
private void control() throws IOException {
|
||||||
// on start, power on the device
|
// on start, power on the device
|
||||||
if (powerOn && displayId == 0 && !Device.isScreenOn(displayId)) {
|
if (powerOn && displayId != Device.DISPLAY_ID_NONE && !Device.isScreenOn()) {
|
||||||
Device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC);
|
Device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC);
|
||||||
|
|
||||||
// dirty hack
|
// dirty hack
|
||||||
@ -281,16 +270,25 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||||
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_SCREEN_POWER_MODE:
|
||||||
if (supportsInputEvents) {
|
if (supportsInputEvents) {
|
||||||
setDisplayPower(msg.getOn());
|
int mode = msg.getAction();
|
||||||
|
boolean setPowerModeOk = Device.setScreenPowerMode(mode);
|
||||||
|
if (setPowerModeOk) {
|
||||||
|
keepPowerModeOff = mode == Device.POWER_MODE_OFF;
|
||||||
|
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
|
||||||
|
if (cleanUp != null) {
|
||||||
|
boolean mustRestoreOnExit = mode != Device.POWER_MODE_NORMAL;
|
||||||
|
cleanUp.setRestoreNormalPowerMode(mustRestoreOnExit);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||||
Device.rotateDevice(getActionDisplayId());
|
Device.rotateDevice(getActionDisplayId());
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_UHID_CREATE:
|
case ControlMessage.TYPE_UHID_CREATE:
|
||||||
getUhidManager().open(msg.getId(), msg.getVendorId(), msg.getProductId(), msg.getText(), msg.getData());
|
getUhidManager().open(msg.getId(), msg.getText(), msg.getData());
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_UHID_INPUT:
|
case ControlMessage.TYPE_UHID_INPUT:
|
||||||
getUhidManager().writeInput(msg.getId(), msg.getData());
|
getUhidManager().writeInput(msg.getId(), msg.getData());
|
||||||
@ -304,9 +302,6 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
case ControlMessage.TYPE_START_APP:
|
case ControlMessage.TYPE_START_APP:
|
||||||
startAppAsync(msg.getText());
|
startAppAsync(msg.getText());
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_RESET_VIDEO:
|
|
||||||
resetVideo();
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
@ -315,9 +310,8 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectKeycode(int action, int keycode, int repeat, int metaState) {
|
private boolean injectKeycode(int action, int keycode, int repeat, int metaState) {
|
||||||
if (keepDisplayPowerOff && action == KeyEvent.ACTION_UP && (keycode == KeyEvent.KEYCODE_POWER || keycode == KeyEvent.KEYCODE_WAKEUP)) {
|
if (keepPowerModeOff && action == KeyEvent.ACTION_UP && (keycode == KeyEvent.KEYCODE_POWER || keycode == KeyEvent.KEYCODE_WAKEUP)) {
|
||||||
assert displayId != Device.DISPLAY_ID_NONE;
|
schedulePowerModeOff();
|
||||||
scheduleDisplayPowerOff(displayId);
|
|
||||||
}
|
}
|
||||||
return injectKeyEvent(action, keycode, repeat, metaState, Device.INJECT_MODE_ASYNC);
|
return injectKeyEvent(action, keycode, repeat, metaState, Device.INJECT_MODE_ASYNC);
|
||||||
}
|
}
|
||||||
@ -351,47 +345,20 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
return successCount;
|
return successCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pair<Point, Integer> getEventPointAndDisplayId(Position position) {
|
|
||||||
// it hides the field on purpose, to read it with atomic access
|
|
||||||
@SuppressWarnings("checkstyle:HiddenField")
|
|
||||||
DisplayData displayData = this.displayData.get();
|
|
||||||
// In scrcpy, displayData should never be null (a touch event can only be generated from the client when a video frame is present).
|
|
||||||
// However, it is possible to send events without video playback when using scrcpy-server alone (except for virtual displays).
|
|
||||||
assert displayData != null || displayId != Device.DISPLAY_ID_NONE : "Cannot receive a positional event without a display";
|
|
||||||
|
|
||||||
Point point;
|
|
||||||
int targetDisplayId;
|
|
||||||
if (displayData != null) {
|
|
||||||
point = displayData.positionMapper.map(position);
|
|
||||||
if (point == null) {
|
|
||||||
if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
|
||||||
Size eventSize = position.getScreenSize();
|
|
||||||
Size currentSize = displayData.positionMapper.getVideoSize();
|
|
||||||
Ln.v("Ignore positional event generated for size " + eventSize + " (current size is " + currentSize + ")");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
targetDisplayId = displayData.virtualDisplayId;
|
|
||||||
} else {
|
|
||||||
// No display, use the raw coordinates
|
|
||||||
point = position.getPoint();
|
|
||||||
targetDisplayId = displayId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Pair.create(point, targetDisplayId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean injectTouch(int action, long pointerId, Position position, float pressure, int actionButton, int buttons) {
|
private boolean injectTouch(int action, long pointerId, Position position, float pressure, int actionButton, int buttons) {
|
||||||
long now = SystemClock.uptimeMillis();
|
long now = SystemClock.uptimeMillis();
|
||||||
|
|
||||||
Pair<Point, Integer> pair = getEventPointAndDisplayId(position);
|
// it hides the field on purpose, to read it with atomic access
|
||||||
if (pair == null) {
|
@SuppressWarnings("checkstyle:HiddenField")
|
||||||
|
DisplayData displayData = this.displayData.get();
|
||||||
|
assert displayData != null : "Cannot receive a touch event without a display";
|
||||||
|
|
||||||
|
Point point = displayData.positionMapper.map(position);
|
||||||
|
if (point == null) {
|
||||||
|
Ln.w("Ignore touch event, it was generated for a different device size");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Point point = pair.first;
|
|
||||||
int targetDisplayId = pair.second;
|
|
||||||
|
|
||||||
int pointerIndex = pointersState.getPointerIndex(pointerId);
|
int pointerIndex = pointersState.getPointerIndex(pointerId);
|
||||||
if (pointerIndex == -1) {
|
if (pointerIndex == -1) {
|
||||||
Ln.w("Too many pointers for touch event");
|
Ln.w("Too many pointers for touch event");
|
||||||
@ -445,7 +412,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
// First button pressed: ACTION_DOWN
|
// First button pressed: ACTION_DOWN
|
||||||
MotionEvent downEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_DOWN, pointerCount, pointerProperties,
|
MotionEvent downEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_DOWN, pointerCount, pointerProperties,
|
||||||
pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0);
|
pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0);
|
||||||
if (!Device.injectEvent(downEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) {
|
if (!Device.injectEvent(downEvent, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -456,7 +423,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
if (!InputManager.setActionButton(pressEvent, actionButton)) {
|
if (!InputManager.setActionButton(pressEvent, actionButton)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!Device.injectEvent(pressEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) {
|
if (!Device.injectEvent(pressEvent, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -470,7 +437,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
if (!InputManager.setActionButton(releaseEvent, actionButton)) {
|
if (!InputManager.setActionButton(releaseEvent, actionButton)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!Device.injectEvent(releaseEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) {
|
if (!Device.injectEvent(releaseEvent, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -478,7 +445,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
// Last button released: ACTION_UP
|
// Last button released: ACTION_UP
|
||||||
MotionEvent upEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_UP, pointerCount, pointerProperties,
|
MotionEvent upEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_UP, pointerCount, pointerProperties,
|
||||||
pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0);
|
pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0);
|
||||||
if (!Device.injectEvent(upEvent, targetDisplayId, Device.INJECT_MODE_ASYNC)) {
|
if (!Device.injectEvent(upEvent, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -489,20 +456,23 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
|
|
||||||
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f,
|
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f,
|
||||||
DEFAULT_DEVICE_ID, 0, source, 0);
|
DEFAULT_DEVICE_ID, 0, source, 0);
|
||||||
return Device.injectEvent(event, targetDisplayId, Device.INJECT_MODE_ASYNC);
|
return Device.injectEvent(event, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectScroll(Position position, float hScroll, float vScroll, int buttons) {
|
private boolean injectScroll(Position position, float hScroll, float vScroll, int buttons) {
|
||||||
long now = SystemClock.uptimeMillis();
|
long now = SystemClock.uptimeMillis();
|
||||||
|
|
||||||
Pair<Point, Integer> pair = getEventPointAndDisplayId(position);
|
// it hides the field on purpose, to read it with atomic access
|
||||||
if (pair == null) {
|
@SuppressWarnings("checkstyle:HiddenField")
|
||||||
|
DisplayData displayData = this.displayData.get();
|
||||||
|
assert displayData != null : "Cannot receive a scroll event without a display";
|
||||||
|
|
||||||
|
Point point = displayData.positionMapper.map(position);
|
||||||
|
if (point == null) {
|
||||||
|
Ln.w("Ignore scroll event, it was generated for a different device size");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Point point = pair.first;
|
|
||||||
int targetDisplayId = pair.second;
|
|
||||||
|
|
||||||
MotionEvent.PointerProperties props = pointerProperties[0];
|
MotionEvent.PointerProperties props = pointerProperties[0];
|
||||||
props.id = 0;
|
props.id = 0;
|
||||||
|
|
||||||
@ -514,21 +484,21 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
|
|
||||||
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f,
|
MotionEvent event = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f,
|
||||||
DEFAULT_DEVICE_ID, 0, InputDevice.SOURCE_MOUSE, 0);
|
DEFAULT_DEVICE_ID, 0, InputDevice.SOURCE_MOUSE, 0);
|
||||||
return Device.injectEvent(event, targetDisplayId, Device.INJECT_MODE_ASYNC);
|
return Device.injectEvent(event, displayData.virtualDisplayId, Device.INJECT_MODE_ASYNC);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedule a call to set display power to off after a small delay.
|
* Schedule a call to set power mode to off after a small delay.
|
||||||
*/
|
*/
|
||||||
private static void scheduleDisplayPowerOff(int displayId) {
|
private static void schedulePowerModeOff() {
|
||||||
EXECUTOR.schedule(() -> {
|
EXECUTOR.schedule(() -> {
|
||||||
Ln.i("Forcing display off");
|
Ln.i("Forcing screen off");
|
||||||
Device.setDisplayPower(displayId, false);
|
Device.setScreenPowerMode(Device.POWER_MODE_OFF);
|
||||||
}, 200, TimeUnit.MILLISECONDS);
|
}, 200, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean pressBackOrTurnScreenOn(int action) {
|
private boolean pressBackOrTurnScreenOn(int action) {
|
||||||
if (displayId == Device.DISPLAY_ID_NONE || Device.isScreenOn(displayId)) {
|
if (Device.isScreenOn()) {
|
||||||
return injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0, Device.INJECT_MODE_ASYNC);
|
return injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0, Device.INJECT_MODE_ASYNC);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -539,9 +509,8 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keepDisplayPowerOff) {
|
if (keepPowerModeOff) {
|
||||||
assert displayId != Device.DISPLAY_ID_NONE;
|
schedulePowerModeOff();
|
||||||
scheduleDisplayPowerOff(displayId);
|
|
||||||
}
|
}
|
||||||
return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC);
|
return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC);
|
||||||
}
|
}
|
||||||
@ -706,26 +675,4 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDisplayPower(boolean on) {
|
|
||||||
// Change the power of the main display when mirroring a virtual display
|
|
||||||
int targetDisplayId = displayId != Device.DISPLAY_ID_NONE ? displayId : 0;
|
|
||||||
boolean setDisplayPowerOk = Device.setDisplayPower(targetDisplayId, on);
|
|
||||||
if (setDisplayPowerOk) {
|
|
||||||
// Do not keep display power off for virtual displays: MOD+p must wake up the physical device
|
|
||||||
keepDisplayPowerOff = displayId != Device.DISPLAY_ID_NONE && !on;
|
|
||||||
Ln.i("Device display turned " + (on ? "on" : "off"));
|
|
||||||
if (cleanUp != null) {
|
|
||||||
boolean mustRestoreOnExit = !on;
|
|
||||||
cleanUp.setRestoreDisplayPower(mustRestoreOnExit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resetVideo() {
|
|
||||||
if (surfaceCapture != null) {
|
|
||||||
Ln.i("Video capture reset");
|
|
||||||
surfaceCapture.requestInvalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,46 +3,46 @@ package com.genymobile.scrcpy.control;
|
|||||||
import com.genymobile.scrcpy.device.Point;
|
import com.genymobile.scrcpy.device.Point;
|
||||||
import com.genymobile.scrcpy.device.Position;
|
import com.genymobile.scrcpy.device.Position;
|
||||||
import com.genymobile.scrcpy.device.Size;
|
import com.genymobile.scrcpy.device.Size;
|
||||||
import com.genymobile.scrcpy.util.AffineMatrix;
|
import com.genymobile.scrcpy.video.ScreenInfo;
|
||||||
|
|
||||||
|
import android.graphics.Rect;
|
||||||
|
|
||||||
public final class PositionMapper {
|
public final class PositionMapper {
|
||||||
|
|
||||||
private final Size videoSize;
|
private final Size videoSize;
|
||||||
private final AffineMatrix videoToDeviceMatrix;
|
private final Rect contentRect;
|
||||||
|
private final int coordsRotation;
|
||||||
|
|
||||||
public PositionMapper(Size videoSize, AffineMatrix videoToDeviceMatrix) {
|
public PositionMapper(Size videoSize, Rect contentRect, int videoRotation) {
|
||||||
this.videoSize = videoSize;
|
this.videoSize = videoSize;
|
||||||
this.videoToDeviceMatrix = videoToDeviceMatrix;
|
this.contentRect = contentRect;
|
||||||
|
this.coordsRotation = reverseRotation(videoRotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PositionMapper create(Size videoSize, AffineMatrix filterTransform, Size targetSize) {
|
public static PositionMapper from(ScreenInfo screenInfo) {
|
||||||
boolean convertToPixels = !videoSize.equals(targetSize) || filterTransform != null;
|
// ignore the locked video orientation, the events will apply in coordinates considered in the physical device orientation
|
||||||
AffineMatrix transform = filterTransform;
|
Size videoSize = screenInfo.getUnlockedVideoSize();
|
||||||
if (convertToPixels) {
|
return new PositionMapper(videoSize, screenInfo.getContentRect(), screenInfo.getVideoRotation());
|
||||||
AffineMatrix inputTransform = AffineMatrix.ndcFromPixels(videoSize);
|
|
||||||
AffineMatrix outputTransform = AffineMatrix.ndcToPixels(targetSize);
|
|
||||||
transform = outputTransform.multiply(transform).multiply(inputTransform);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PositionMapper(videoSize, transform);
|
private static int reverseRotation(int rotation) {
|
||||||
}
|
return (4 - rotation) % 4;
|
||||||
|
|
||||||
public Size getVideoSize() {
|
|
||||||
return videoSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Point map(Position position) {
|
public Point map(Position position) {
|
||||||
Size clientVideoSize = position.getScreenSize();
|
// reverse the video rotation to apply the events
|
||||||
|
Position devicePosition = position.rotate(coordsRotation);
|
||||||
|
|
||||||
|
Size clientVideoSize = devicePosition.getScreenSize();
|
||||||
if (!videoSize.equals(clientVideoSize)) {
|
if (!videoSize.equals(clientVideoSize)) {
|
||||||
// The client sends a click relative to a video with wrong dimensions,
|
// The client sends a click relative to a video with wrong dimensions,
|
||||||
// the device may have been rotated since the event was generated, so ignore the event
|
// the device may have been rotated since the event was generated, so ignore the event
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Point point = position.getPoint();
|
Point point = devicePosition.getPoint();
|
||||||
if (videoToDeviceMatrix != null) {
|
int convertedX = contentRect.left + point.getX() * contentRect.width() / videoSize.getWidth();
|
||||||
point = videoToDeviceMatrix.apply(point);
|
int convertedY = contentRect.top + point.getY() * contentRect.height() / videoSize.getHeight();
|
||||||
}
|
return new Point(convertedX, convertedY);
|
||||||
return point;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ public final class UhidManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void open(int id, int vendorId, int productId, String name, byte[] reportDesc) throws IOException {
|
public void open(int id, String name, byte[] reportDesc) throws IOException {
|
||||||
try {
|
try {
|
||||||
FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0);
|
FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0);
|
||||||
try {
|
try {
|
||||||
@ -58,7 +58,7 @@ public final class UhidManager {
|
|||||||
close(old);
|
close(old);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] req = buildUhidCreate2Req(vendorId, productId, name, reportDesc);
|
byte[] req = buildUhidCreate2Req(name, reportDesc);
|
||||||
Os.write(fd, req, 0, req.length);
|
Os.write(fd, req, 0, req.length);
|
||||||
|
|
||||||
registerUhidListener(id, fd);
|
registerUhidListener(id, fd);
|
||||||
@ -148,7 +148,7 @@ public final class UhidManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] buildUhidCreate2Req(int vendorId, int productId, String name, byte[] reportDesc) {
|
private static byte[] buildUhidCreate2Req(String name, byte[] reportDesc) {
|
||||||
/*
|
/*
|
||||||
* struct uhid_event {
|
* struct uhid_event {
|
||||||
* uint32_t type;
|
* uint32_t type;
|
||||||
@ -174,7 +174,7 @@ public final class UhidManager {
|
|||||||
ByteBuffer buf = ByteBuffer.allocate(280 + reportDesc.length).order(ByteOrder.nativeOrder());
|
ByteBuffer buf = ByteBuffer.allocate(280 + reportDesc.length).order(ByteOrder.nativeOrder());
|
||||||
buf.putInt(UHID_CREATE2);
|
buf.putInt(UHID_CREATE2);
|
||||||
|
|
||||||
String actualName = name.isEmpty() ? "scrcpy" : name;
|
String actualName = name.isEmpty() ? "scrcpy" : "scrcpy: " + name;
|
||||||
byte[] utf8Name = actualName.getBytes(StandardCharsets.UTF_8);
|
byte[] utf8Name = actualName.getBytes(StandardCharsets.UTF_8);
|
||||||
int len = StringUtils.getUtf8TruncationIndex(utf8Name, 127);
|
int len = StringUtils.getUtf8TruncationIndex(utf8Name, 127);
|
||||||
assert len <= 127;
|
assert len <= 127;
|
||||||
@ -183,8 +183,8 @@ public final class UhidManager {
|
|||||||
|
|
||||||
buf.putShort((short) reportDesc.length);
|
buf.putShort((short) reportDesc.length);
|
||||||
buf.putShort(BUS_VIRTUAL);
|
buf.putShort(BUS_VIRTUAL);
|
||||||
buf.putInt(vendorId);
|
buf.putInt(0); // vendor id
|
||||||
buf.putInt(productId);
|
buf.putInt(0); // product id
|
||||||
buf.putInt(0); // version
|
buf.putInt(0); // version
|
||||||
buf.putInt(0); // country;
|
buf.putInt(0); // country;
|
||||||
buf.put(reportDesc);
|
buf.put(reportDesc);
|
||||||
|
@ -40,9 +40,8 @@ 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:
|
public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1;
|
||||||
// <https://github.com/Genymobile/scrcpy/issues/5530>
|
public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
|
||||||
private static final boolean USE_ANDROID_15_DISPLAY_POWER = false;
|
|
||||||
|
|
||||||
private Device() {
|
private Device() {
|
||||||
// not instantiable
|
// not instantiable
|
||||||
@ -81,9 +80,8 @@ public final class Device {
|
|||||||
&& injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId, injectMode);
|
&& injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId, injectMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isScreenOn(int displayId) {
|
public static boolean isScreenOn() {
|
||||||
assert displayId != DISPLAY_ID_NONE;
|
return ServiceManager.getPowerManager().isScreenOn();
|
||||||
return ServiceManager.getPowerManager().isScreenOn(displayId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void expandNotificationPanel() {
|
public static void expandNotificationPanel() {
|
||||||
@ -128,13 +126,10 @@ public final class Device {
|
|||||||
return clipboardManager.setText(text);
|
return clipboardManager.setText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean setDisplayPower(int displayId, boolean on) {
|
/**
|
||||||
assert displayId != Device.DISPLAY_ID_NONE;
|
* @param mode one of the {@code POWER_MODE_*} constants
|
||||||
|
*/
|
||||||
if (USE_ANDROID_15_DISPLAY_POWER && Build.VERSION.SDK_INT >= AndroidVersions.API_35_ANDROID_15) {
|
public static boolean setScreenPowerMode(int mode) {
|
||||||
return ServiceManager.getDisplayManager().requestDisplayPower(displayId, on);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean applyToMultiPhysicalDisplays = Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10;
|
boolean applyToMultiPhysicalDisplays = Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10;
|
||||||
|
|
||||||
if (applyToMultiPhysicalDisplays
|
if (applyToMultiPhysicalDisplays
|
||||||
@ -147,7 +142,6 @@ public final class Device {
|
|||||||
applyToMultiPhysicalDisplays = false;
|
applyToMultiPhysicalDisplays = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int mode = on ? POWER_MODE_NORMAL : POWER_MODE_OFF;
|
|
||||||
if (applyToMultiPhysicalDisplays) {
|
if (applyToMultiPhysicalDisplays) {
|
||||||
// On Android 14, these internal methods have been moved to DisplayControl
|
// On Android 14, these internal methods have been moved to DisplayControl
|
||||||
boolean useDisplayControl =
|
boolean useDisplayControl =
|
||||||
@ -181,7 +175,7 @@ public final class Device {
|
|||||||
public static boolean powerOffScreen(int displayId) {
|
public static boolean powerOffScreen(int displayId) {
|
||||||
assert displayId != DISPLAY_ID_NONE;
|
assert displayId != DISPLAY_ID_NONE;
|
||||||
|
|
||||||
if (!isScreenOn(displayId)) {
|
if (!isScreenOn()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC);
|
return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC);
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
package com.genymobile.scrcpy.device;
|
|
||||||
|
|
||||||
public enum Orientation {
|
|
||||||
|
|
||||||
// @formatter:off
|
|
||||||
Orient0("0"),
|
|
||||||
Orient90("90"),
|
|
||||||
Orient180("180"),
|
|
||||||
Orient270("270"),
|
|
||||||
Flip0("flip0"),
|
|
||||||
Flip90("flip90"),
|
|
||||||
Flip180("flip180"),
|
|
||||||
Flip270("flip270");
|
|
||||||
|
|
||||||
public enum Lock {
|
|
||||||
Unlocked, LockedInitial, LockedValue,
|
|
||||||
}
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
Orientation(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Orientation getByName(String name) {
|
|
||||||
for (Orientation orientation : values()) {
|
|
||||||
if (orientation.name.equals(name)) {
|
|
||||||
return orientation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalArgumentException("Unknown orientation: " + name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Orientation fromRotation(int rotation) {
|
|
||||||
assert rotation >= 0 && rotation < 4;
|
|
||||||
return values()[rotation];
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFlipped() {
|
|
||||||
return (ordinal() & 4) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRotation() {
|
|
||||||
return ordinal() & 3;
|
|
||||||
}
|
|
||||||
}
|
|
@ -29,61 +29,6 @@ public final class Size {
|
|||||||
return new Size(height, width);
|
return new Size(height, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Size limit(int maxSize) {
|
|
||||||
assert maxSize >= 0 : "Max size may not be negative";
|
|
||||||
assert maxSize % 8 == 0 : "Max size must be a multiple of 8";
|
|
||||||
|
|
||||||
if (maxSize == 0) {
|
|
||||||
// No limit
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean portrait = height > width;
|
|
||||||
int major = portrait ? height : width;
|
|
||||||
if (major <= maxSize) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
int minor = portrait ? width : height;
|
|
||||||
|
|
||||||
int newMajor = maxSize;
|
|
||||||
int newMinor = maxSize * minor / major;
|
|
||||||
|
|
||||||
int w = portrait ? newMinor : newMajor;
|
|
||||||
int h = portrait ? newMajor : newMinor;
|
|
||||||
return new Size(w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Round both dimensions of this size to be a multiple of 8 (as required by many encoders).
|
|
||||||
*
|
|
||||||
* @return The current size rounded.
|
|
||||||
*/
|
|
||||||
public Size round8() {
|
|
||||||
if (isMultipleOf8()) {
|
|
||||||
// Already a multiple of 8
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean portrait = height > width;
|
|
||||||
int major = portrait ? height : width;
|
|
||||||
int minor = portrait ? width : height;
|
|
||||||
|
|
||||||
major &= ~7; // round down to not exceed the initial size
|
|
||||||
minor = (minor + 4) & ~7; // round to the nearest to minimize aspect ratio distortion
|
|
||||||
if (minor > major) {
|
|
||||||
minor = major;
|
|
||||||
}
|
|
||||||
|
|
||||||
int w = portrait ? minor : major;
|
|
||||||
int h = portrait ? major : minor;
|
|
||||||
return new Size(w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isMultipleOf8() {
|
|
||||||
return (width & 7) == 0 && (height & 7) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Rect toRect() {
|
public Rect toRect() {
|
||||||
return new Rect(0, 0, width, height);
|
return new Rect(0, 0, width, height);
|
||||||
}
|
}
|
||||||
@ -107,6 +52,6 @@ public final class Size {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return width + "x" + height;
|
return "Size{" + "width=" + width + ", height=" + height + '}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user