Compare commits

..

3 Commits

Author SHA1 Message Date
Romain Vimont
2bbf650758 Implement audio forwarding
Use Android Open Accessory 2 to redirect the device audio output to the
computer, creating a new audio input source.

Record this new source and play it to the default output.

<https://source.android.com/devices/accessories/aoa2#audio-support>
2018-04-12 13:33:17 +02:00
Romain Vimont
36a94137b5 Expose functions to get device serial and model
Expose functions to retrieve the device serial and model, as returned by
"adb get-serialno" and "adb shell getprop ro.product.model".

These functions require to execute a process with output redirection, so
also implement the necessary functions for both Unix and Windows.
2018-04-12 13:32:32 +02:00
Romain Vimont
14112d8b11 Initialize only video subsystem in screen.c
SDL may initialize several subsystems (e.g. video and audio). Initialize
only video in screen.c, and call SQL_Quit in any case.
2018-04-12 13:32:32 +02:00
65 changed files with 1275 additions and 1536 deletions

236
BUILD.md
View File

@@ -1,236 +0,0 @@
# Build scrcpy
Here are the instructions to build _scrcpy_ (client and server).
You may want to build only the client: the server binary, which will be pushed
to the Android device, does not depend on your system and architecture. In that
case, use the [prebuilt server] (so you will not need Java or the Android SDK).
[prebuilt server]: #prebuilt-server
## Requirements
You need [adb]. It is available in the [Android SDK platform
tools][platform-tools], or packaged in your distribution (`android-adb-tools`).
On Windows, download the [platform-tools][platform-tools-windows] and extract
the following files to a directory accessible from your `PATH`:
- `adb.exe`
- `AdbWinApi.dll`
- `AdbWinUsbApi.dll`
The client requires [FFmpeg] and [LibSDL2]. Just follow the instructions.
[adb]: https://developer.android.com/studio/command-line/adb.html
[platform-tools]: https://developer.android.com/studio/releases/platform-tools.html
[platform-tools-windows]: https://dl.google.com/android/repository/platform-tools-latest-windows.zip
[ffmpeg]: https://en.wikipedia.org/wiki/FFmpeg
[LibSDL2]: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
## System-specific steps
### Linux
Install the required packages from your package manager.
#### Debian/Ubuntu
```bash
# runtime dependencies
sudo apt install ffmpeg libsdl2-2.0.0
# client build dependencies
sudo apt install make gcc pkg-config meson \
libavcodec-dev libavformat-dev libavutil-dev \
libsdl2-dev
# server build dependencies
sudo apt install openjdk-8-jdk
```
#### Fedora
```bash
# enable RPM fusion free
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
# client build dependencies
sudo dnf install SDL2-devel ffms2-devel meson gcc make
# server build dependencies
sudo dnf install java
```
### Windows
#### Cross-compile from Linux
This is the preferred method (and the way the release is built).
From _Debian_, install _mingw_:
```bash
sudo apt install mingw-w64 mingw-w64-tools
```
You also need the JDK to build the server:
```bash
sudo apt install openjdk-8-jdk
```
Then generate the releases:
```bash
make -f Makefile.CrossWindows
```
It will generate win32 and win64 releases into `dist/`.
#### In MSYS2
From Windows, you need [MSYS2] to build the project. From an MSYS2 terminal,
install the required packages:
[MSYS2]: http://www.msys2.org/
```bash
# runtime dependencies
pacman -S mingw-w64-x86_64-SDL2 \
mingw-w64-x86_64-ffmpeg
# client build dependencies
pacman -S mingw-w64-x86_64-make \
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-pkg-config \
mingw-w64-x86_64-meson
```
For a 32 bits version, replace `x86_64` by `i686`:
```bash
# runtime dependencies
pacman -S mingw-w64-i686-SDL2 \
mingw-w64-i686-ffmpeg
# client build dependencies
pacman -S mingw-w64-i686-make \
mingw-w64-i686-gcc \
mingw-w64-i686-pkg-config \
mingw-w64-i686-meson
```
Java (>= 7) is not available in MSYS2, so if you plan to build the server,
install it manually and make it available from the `PATH`:
```bash
export PATH="$JAVA_HOME/bin:$PATH"
```
### Mac OS
Install the packages with [Homebrew]:
[Homebrew]: https://brew.sh/
```bash
# runtime dependencies
brew install sdl2 ffmpeg
# client build dependencies
brew install pkg-config meson
```
Additionally, if you want to build the server, install Java 8 from Caskroom, and
make it avaliable from the `PATH`:
```bash
brew tap caskroom/versions
brew cask install java8
export JAVA_HOME="$(/usr/libexec/java_home --version 1.8)"
export PATH="$JAVA_HOME/bin:$PATH"
```
### Docker
See [pierlon/scrcpy-docker](https://github.com/pierlon/scrcpy-docker).
## Common steps
If you want to build the server, install the [Android SDK] (_Android Studio_),
and set `ANDROID_HOME` to its directory. For example:
[Android SDK]: https://developer.android.com/studio/index.html
```bash
export ANDROID_HOME=~/android/sdk
```
If you don't want to build the server, use the [prebuilt server].
Clone the project:
```bash
git clone https://github.com/Genymobile/scrcpy
cd scrcpy
```
Then, build:
```bash
meson x --buildtype release --strip -Db_lto=true
cd x
ninja
```
### Run
To run without installing:
```bash
./run x [options]
```
### Install
After a successful build, you can install _scrcpy_ on the system:
```bash
sudo ninja install # without sudo on Windows
```
This installs two files:
- `/usr/local/bin/scrcpy`
- `/usr/local/share/scrcpy/scrcpy-server.jar`
Just remove them to "uninstall" the application.
You can then [run](README.md#run) _scrcpy_.
## Prebuilt server
- [`scrcpy-server-v1.4.jar`][direct-scrcpy-server]
_(SHA-256: 1ff7a72fcfe81dadccfab9d6f86c971cd7c7f38f17196748fe05480e301b443d)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.4/scrcpy-server-v1.4.jar
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:
```bash
meson x --buildtype release --strip -Db_lto=true \
-Dprebuilt_server=/path/to/scrcpy-server.jar
cd x
ninja
sudo ninja install
```

26
FAQ.md
View File

@@ -7,8 +7,29 @@ with it.
Here are the common reported problems and their status.
### On Windows, I have no output in the console
### On Windows, my device is not detected
When run in `cmd.exe`, the application does not print anything. Even `scrcpy
--help` have no output. We don't know why yet.
However, if you run the very same `scrcpy.exe` from
[MSYS2](https://www.msys2.org/) (`mingw64`), then it correctly prints output.
As a workaround, redirect outputs to files, so that you can read the files
afterwards:
```bash
scrcpy >stdout 2>stderr
type stdout
type stderr
```
_Note that all SDL logs are printed to stderr._
### On Windows, when I start the application, nothing happens
The previous problem does not help to get a clue about the cause.
The most common is your device not being detected by `adb`, or is unauthorized.
Check everything is ok by calling:
@@ -26,11 +47,10 @@ If you still encounter problems, please see [issue 9].
### I get a black screen for some applications like Silence
This is expected, they requested to [protect] the screen.
This is expected, they requested to protect the screen.
In [Silence], you can disable it in settings → Privacy → Screen security.
[protect]: https://developer.android.com/reference/android/view/Display#FLAG_SECURE
[silence]: https://f-droid.org/en/packages/org.smssecure.smssecure/
See [issue 36].

65
Makefile Normal file
View File

@@ -0,0 +1,65 @@
# This makefile provides recipes to build a "portable" version of scrcpy.
#
# 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.jar).
#
# In particular, this implies to change the location from where the client push
# the server to the device.
#
# "make release-portable" builds a zip containing the client and the server.
#
# On Windows with MSYS2/mingw64, execute:
# GRADLE="$PWD/gradlew" mingw32-make release-portable
#
# This is a simple Makefile because Meson is not flexible enough to execute some
# arbitrary commands.
.PHONY: default clean build-portable release-portable dist-portable dist-portable-zip sums test check
GRADLE ?= ./gradlew
PORTABLE_BUILD_DIR := build-portable
DIST := dist
TARGET_DIR := scrcpy
VERSION := $(shell git describe --tags --always)
TARGET := $(TARGET_DIR)-$(VERSION).zip
default:
@echo 'You must specify a target. Try: make release-portable'
clean:
$(GRADLE) clean
rm -rf "$(PORTABLE_BUILD_DIR)" "$(DIST)"
build-portable:
[ -d "$(PORTABLE_BUILD_DIR)" ] || ( mkdir "$(PORTABLE_BUILD_DIR)" && \
meson "$(PORTABLE_BUILD_DIR)" \
--buildtype release --strip -Db_lto=true \
-Doverride_server_path=scrcpy-server.jar )
ninja -C "$(PORTABLE_BUILD_DIR)"
release-portable: clean dist-portable-zip sums
@echo "Release created in $(DIST)/."
dist-portable: build-portable
mkdir -p "$(DIST)/$(TARGET_DIR)"
cp "$(PORTABLE_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(TARGET_DIR)/"
cp "$(PORTABLE_BUILD_DIR)"/app/scrcpy "$(DIST)/$(TARGET_DIR)/"
dist-portable-zip: dist-portable
cd "$(DIST)"; \
zip -r "$(TARGET)" "$(TARGET_DIR)"
sums:
cd "$(DIST)"; \
sha256sum *.zip > SHA256SUM.txt
test: build-portable
$(GRADLE) test
ninja -C "$(PORTABLE_BUILD_DIR)" test
check: build-portable
$(GRADLE) check
ninja -C "$(PORTABLE_BUILD_DIR)" test

View File

@@ -1,136 +0,0 @@
# This makefile provides recipes to build a "portable" version of scrcpy for
# Windows.
#
# Here, "portable" means that the client and server binaries are expected to be
# anywhere, but in the same directory, instead of well-defined separate
# locations (e.g. /usr/bin/scrcpy and /usr/share/scrcpy/scrcpy-server.jar).
#
# In particular, this implies to change the location from where the client push
# the server to the device.
.PHONY: default clean \
build-server \
prepare-deps-win32 prepare-deps-win64 \
build-win32 build-win32-noconsole \
build-win64 build-win64-noconsole \
dist-win32 dist-win64 \
zip-win32 zip-win64 \
sums release
GRADLE ?= ./gradlew
SERVER_BUILD_DIR := build-server
WIN32_BUILD_DIR := build-win32
WIN32_NOCONSOLE_BUILD_DIR := build-win32-noconsole
WIN64_BUILD_DIR := build-win64
WIN64_NOCONSOLE_BUILD_DIR := build-win64-noconsole
DIST := dist
WIN32_TARGET_DIR := scrcpy-win32
WIN64_TARGET_DIR := scrcpy-win64
VERSION := $(shell git describe --tags --always)
WIN32_TARGET := $(WIN32_TARGET_DIR)-$(VERSION).zip
WIN64_TARGET := $(WIN64_TARGET_DIR)-$(VERSION).zip
release: clean zip-win32 zip-win64 sums
@echo "Release created in $(DIST)/."
clean:
$(GRADLE) clean
rm -rf "$(SERVER_BUILD_DIR)" "$(WIN32_BUILD_DIR)" "$(WIN64_BUILD_DIR)" \
"$(WIN32_NOCONSOLE_BUILD_DIR)" "$(WIN64_NOCONSOLE_BUILD_DIR)" "$(DIST)"
build-server:
[ -d "$(SERVER_BUILD_DIR)" ] || ( mkdir "$(SERVER_BUILD_DIR)" && \
meson "$(SERVER_BUILD_DIR)" \
--buildtype release -Dbuild_app=false )
ninja -C "$(SERVER_BUILD_DIR)"
prepare-deps-win32:
-$(MAKE) -C prebuilt-deps prepare-win32
build-win32: prepare-deps-win32
[ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \
meson "$(WIN32_BUILD_DIR)" \
--cross-file cross_win32.txt \
--buildtype release --strip -Db_lto=true \
-Dcrossbuild_windows=true \
-Dbuild_server=false \
-Doverride_server_path=scrcpy-server.jar )
ninja -C "$(WIN32_BUILD_DIR)"
build-win32-noconsole: prepare-deps-win32
[ -d "$(WIN32_NOCONSOLE_BUILD_DIR)" ] || ( mkdir "$(WIN32_NOCONSOLE_BUILD_DIR)" && \
meson "$(WIN32_NOCONSOLE_BUILD_DIR)" \
--cross-file cross_win32.txt \
--buildtype release --strip -Db_lto=true \
-Dcrossbuild_windows=true \
-Dbuild_server=false \
-Dwindows_noconsole=true \
-Doverride_server_path=scrcpy-server.jar )
ninja -C "$(WIN32_NOCONSOLE_BUILD_DIR)"
prepare-deps-win64:
-$(MAKE) -C prebuilt-deps prepare-win64
build-win64: prepare-deps-win64
[ -d "$(WIN64_BUILD_DIR)" ] || ( mkdir "$(WIN64_BUILD_DIR)" && \
meson "$(WIN64_BUILD_DIR)" \
--cross-file cross_win64.txt \
--buildtype release --strip -Db_lto=true \
-Dcrossbuild_windows=true \
-Dbuild_server=false \
-Doverride_server_path=scrcpy-server.jar )
ninja -C "$(WIN64_BUILD_DIR)"
build-win64-noconsole: prepare-deps-win64
[ -d "$(WIN64_NOCONSOLE_BUILD_DIR)" ] || ( mkdir "$(WIN64_NOCONSOLE_BUILD_DIR)" && \
meson "$(WIN64_NOCONSOLE_BUILD_DIR)" \
--cross-file cross_win64.txt \
--buildtype release --strip -Db_lto=true \
-Dcrossbuild_windows=true \
-Dbuild_server=false \
-Dwindows_noconsole=true \
-Doverride_server_path=scrcpy-server.jar )
ninja -C "$(WIN64_NOCONSOLE_BUILD_DIR)"
dist-win32: build-server build-win32 build-win32-noconsole
mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(WIN32_TARGET_DIR)/"
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe"
cp prebuilt-deps/ffmpeg-4.0.2-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0.2-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0.2-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0.2-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.8/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
dist-win64: build-server build-win64 build-win64-noconsole
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(WIN64_TARGET_DIR)/"
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe"
cp prebuilt-deps/ffmpeg-4.0.2-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0.2-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0.2-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0.2-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.8/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
zip-win32: dist-win32
cd "$(DIST)"; \
zip -r "$(WIN32_TARGET)" "$(WIN32_TARGET_DIR)"
zip-win64: dist-win64
cd "$(DIST)"; \
zip -r "$(WIN64_TARGET)" "$(WIN64_TARGET_DIR)"
sums:
cd "$(DIST)"; \
sha256sum *.zip > SHA256SUMS.txt

256
README.md
View File

@@ -1,8 +1,8 @@
# scrcpy (v1.4)
# scrcpy
This application provides display and control of Android devices connected on
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
It works on _GNU/Linux_, _Windows_ and _MacOS_.
USB. It does not require any _root_ access. It works on _GNU/Linux_, _Windows_
and _MacOS_.
![screenshot](assets/screenshot-debian-600.jpg)
@@ -11,49 +11,126 @@ It works on _GNU/Linux_, _Windows_ and _MacOS_.
The Android part requires at least API 21 (Android 5.0).
You need [adb]. It is available in the [Android SDK platform
tools][platform-tools], or packaged in your distribution (`android-adb-tools`).
On Windows, if you use the [prebuilt application](#windows), it is already
included. Otherwise, just download the [platform-tools][platform-tools-windows]
and extract the following files to a directory accessible from your `PATH`:
- `adb.exe`
- `AdbWinApi.dll`
- `AdbWinUsbApi.dll`
Make sure you [enabled adb debugging][enable-adb] on your device(s).
The client requires [FFmpeg], [LibSDL2] and [LibUSB].
[adb]: https://developer.android.com/studio/command-line/adb.html
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
[platform-tools]: https://developer.android.com/studio/releases/platform-tools.html
[platform-tools-windows]: https://dl.google.com/android/repository/platform-tools-latest-windows.zip
[ffmpeg]: https://en.wikipedia.org/wiki/FFmpeg
[libsdl2]: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
[libusb]: https://en.wikipedia.org/wiki/Libusb
## Build and install
## Get the app
### System-specific steps
#### Linux
### Linux
Install the required packages from your package manager.
On Linux, you typically need to [build the app manually][BUILD]. Don't worry,
it's not that hard.
##### Debian/Ubuntu
For Arch Linux, two [AUR] packages have been created by users:
```bash
# runtime dependencies
sudo apt install ffmpeg libsdl2-2.0.0 libusb-1.0-0
# client build dependencies
sudo apt install make gcc pkg-config meson \
libavcodec-dev libavformat-dev libavutil-dev \
libsdl2-dev libusb-1.0-0-dev
# server build dependencies
sudo apt install openjdk-8-jdk
```
##### Fedora
```bash
# enable RPM fusion free
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
# client build dependencies
sudo dnf install SDL2-devel ffms2-devel meson gcc make
# server build dependencies
sudo dnf install java
```
##### Arch Linux
Two [AUR] packages have been created by users:
- [`scrcpy`](https://aur.archlinux.org/packages/scrcpy/)
- [`scrcpy-prebuiltserver`](https://aur.archlinux.org/packages/scrcpy-prebuiltserver/)
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
#### Windows
For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available:
### Windows
- [`scrcpy-windows-with-deps-v1.1.zip`][direct-windows-with-deps].
_(SHA-256: 27eb36c15937601d1062c1dc0b45faae0e06fefea2019aadeb4fa7f76a07bb4c)_
For Windows, for simplicity, prebuilt archives with all the dependencies
(including `adb`) are available:
[direct-windows-with-deps]: https://github.com/Genymobile/scrcpy/releases/download/v1.1/scrcpy-windows-with-deps-v1.1.zip
- [`scrcpy-win32-v1.4.zip`][direct-win32]
_(SHA-256: 1f72fa520980727e8943b7214b64c66b00b9b5267f7cffefb64fa37c3ca803cf)_
- [`scrcpy-win64-v1.4.zip`][direct-win64]
_(SHA-256: 382f02bd8ed3db2cc7ab15aabdb83674744993b936d602b01e6959a150584a79)_
_(It's just a portable version including _dll_ copied from MSYS2.)_
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.4/scrcpy-win32-v1.4.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.4/scrcpy-win64-v1.4.zip
Instead, you may want to build it manually. You need [MSYS2] to build the
project. From an MSYS2 terminal, install the required packages:
You can also [build the app manually][BUILD].
[MSYS2]: http://www.msys2.org/
```bash
# runtime dependencies
pacman -S mingw-w64-x86_64-SDL2 \
mingw-w64-x86_64-ffmpeg
### Mac OS
# client build dependencies
pacman -S mingw-w64-x86_64-make \
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-pkg-config \
mingw-w64-x86_64-meson \
mingw-w64-x86_64-libusb
```
For a 32 bits version, replace `x86_64` by `i686`:
```bash
# runtime dependencies
pacman -S mingw-w64-i686-SDL2 \
mingw-w64-i686-ffmpeg
# client build dependencies
pacman -S mingw-w64-i686-make \
mingw-w64-i686-gcc \
mingw-w64-i686-pkg-config \
mingw-w64-i686-meson
```
Java (>= 7) is not available in MSYS2, so if you plan to build the server,
install it manually and make it available from the `PATH`:
```bash
export PATH="$JAVA_HOME/bin:$PATH"
```
#### Mac OS
The application is available in [Homebrew]. Just install it:
@@ -63,18 +140,106 @@ The application is available in [Homebrew]. Just install it:
brew install scrcpy
```
You need `adb`, accessible from your `PATH`. If you don't have it yet:
Instead, you may want to build it manually. Install the packages:
```bash
brew cask install android-platform-tools
# runtime dependencies
brew install sdl2 ffmpeg
# client build dependencies
brew install pkg-config meson libusb
```
You can also [build the app manually][BUILD].
Additionally, if you want to build the server, install Java 8 from Caskroom, and
make it avaliable from the `PATH`:
```bash
brew tap caskroom/versions
brew cask install java8
export JAVA_HOME="$(/usr/libexec/java_home --version 1.8)"
export PATH="$JAVA_HOME/bin:$PATH"
```
#### Docker
See [pierlon/scrcpy-docker](https://github.com/pierlon/scrcpy-docker).
### Common steps
Install the [Android SDK] (_Android Studio_), and set `ANDROID_HOME` to
its directory. For example:
[Android SDK]: https://developer.android.com/studio/index.html
```bash
export ANDROID_HOME=~/android/sdk
```
Clone the project:
```bash
git clone https://github.com/Genymobile/scrcpy
cd scrcpy
```
Then, build:
```bash
meson x --buildtype release --strip -Db_lto=true
cd x
ninja
```
You can test it from here:
```bash
ninja run
```
Or you can install it on the system:
```bash
sudo ninja install # without sudo on Windows
```
This installs two files:
- `/usr/local/bin/scrcpy`
- `/usr/local/share/scrcpy/scrcpy-server.jar`
Just remove them to "uninstall" the application.
#### Prebuilt server
Since the server binary, that will be pushed to the Android device, does not
depend on your system and architecture, you may want to use the prebuilt binary
instead:
- [`scrcpy-server-v1.1.jar`][direct-scrcpy-server].
_(SHA-256: 14826512bf38447ec94adf3b531676ce038d19e7e06757fb4e537882b17e77b3)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.1/scrcpy-server-v1.1.jar
In that case, the build does not require Java or the Android SDK.
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:
```bash
meson x --buildtype release --strip -Db_lto=true \
-Dprebuilt_server=/path/to/scrcpy-server.jar
cd x
ninja
sudo ninja install
```
## Run
Plug an Android device, and execute:
_At runtime, `adb` must be accessible from your `PATH`._
If everything is ok, just plug an Android device, and execute:
```bash
scrcpy
@@ -99,12 +264,6 @@ screen is smaller, or cannot decode such a high definition):
scrcpy -m 1024
```
The device screen may be cropped to mirror only part of the screen:
```bash
scrcpy -c 1224:1440:0:0 # 1224x1440 at offset (0,0)
```
If several devices are listed in `adb devices`, you must specify the _serial_:
```bash
@@ -117,12 +276,21 @@ To show physical touches while scrcpy is running:
scrcpy -t
```
The app may be started directly in fullscreen:
To enable audio forwarding:
```bash
scrcpy -a
```
scrcpy -f
To run without installing:
```bash
./run x [options]
```
(where `x` is your build directory).
## Shortcuts
| Action | Shortcut |
@@ -134,14 +302,12 @@ scrcpy -f
| click on `BACK` | `Ctrl`+`b` \| _Right-click²_ |
| click on `APP_SWITCH` | `Ctrl`+`s` |
| click on `MENU` | `Ctrl`+`m` |
| click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ (`Cmd`+`↑` on MacOS) |
| click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ (`Cmd`+`↓` on MacOS) |
| click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ |
| click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ |
| click on `POWER` | `Ctrl`+`p` |
| turn screen on | _Right-click²_ |
| paste computer clipboard to device | `Ctrl`+`v` |
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
| install APK from computer | drag & drop APK file |
| push file to `/sdcard/` | drag & drop non-APK file |
_¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._
@@ -157,13 +323,6 @@ A colleague challenged me to find a name as unpronounceable as [gnirehtet].
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## How to build?
See [BUILD].
[BUILD]: BUILD.md
## Common issues
See the [FAQ](FAQ.md).
@@ -194,8 +353,5 @@ Read the [developers page].
## Articles
- [Introducing scrcpy][article-intro]
- [Scrcpy now works wirelessly][article-tcpip]
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
- [Introducing scrcpy](https://blog.rom1v.com/2018/03/introducing-scrcpy/)
- [Scrcpy now works wirelessly](https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/)

View File

@@ -1,71 +1,39 @@
src = [
'src/main.c',
'src/command.c',
'src/control_event.c',
'src/controlevent.c',
'src/controller.c',
'src/convert.c',
'src/decoder.c',
'src/device.c',
'src/file_handler.c',
'src/fps_counter.c',
'src/fpscounter.c',
'src/frames.c',
'src/input_manager.c',
'src/lock_util.c',
'src/inputmanager.c',
'src/lockutil.c',
'src/net.c',
'src/scrcpy.c',
'src/screen.c',
'src/server.c',
'src/str_util.c',
'src/tiny_xpm.c',
'src/strutil.c',
'src/tinyxpm.c',
]
if not get_option('crossbuild_windows')
# native build
dependencies = [
dependency('libavformat'),
dependency('libavcodec'),
dependency('libavutil'),
dependency('sdl2'),
if get_option('audio_support')
src += [
'src/aoa.c',
'src/audio.c'
]
endif
else
# cross-compile mingw32 build (from Linux to Windows)
cc = meson.get_compiler('c')
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin'
sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/lib'
sdl2_include_dir = '../prebuilt-deps/' + prebuilt_sdl2 + '/include'
sdl2 = declare_dependency(
dependencies: [
cc.find_library('SDL2', dirs: sdl2_bin_dir),
cc.find_library('SDL2main', dirs: sdl2_lib_dir),
],
include_directories: include_directories(sdl2_include_dir)
)
prebuilt_ffmpeg_shared = meson.get_cross_property('prebuilt_ffmpeg_shared')
prebuilt_ffmpeg_dev = meson.get_cross_property('prebuilt_ffmpeg_dev')
ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg_shared + '/bin'
ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg_dev + '/include'
ffmpeg = declare_dependency(
dependencies: [
cc.find_library('avcodec-58', dirs: ffmpeg_bin_dir),
cc.find_library('avformat-58', dirs: ffmpeg_bin_dir),
cc.find_library('avutil-56', dirs: ffmpeg_bin_dir),
],
include_directories: include_directories(ffmpeg_include_dir)
)
dependencies = [
ffmpeg,
sdl2,
cc.find_library('mingw32')
]
dependencies = [
dependency('libavformat'),
dependency('libavcodec'),
dependency('libavutil'),
dependency('sdl2'),
]
if get_option('audio_support')
dependencies += dependency('libusb-1.0')
endif
cc = meson.get_compiler('c')
@@ -85,7 +53,7 @@ conf = configuration_data()
conf.set('BUILD_DEBUG', get_option('buildtype') == 'debug')
# the version, updated on release
conf.set_quoted('SCRCPY_VERSION', '1.4')
conf.set_quoted('SCRCPY_VERSION', '1.1')
# the prefix used during configuration (meson --prefix=PREFIX)
conf.set_quoted('PREFIX', get_option('prefix'))
@@ -126,30 +94,21 @@ conf.set('SKIP_FRAMES', get_option('skip_frames'))
# enable High DPI support
conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
# disable console on Windows
conf.set('WINDOWS_NOCONSOLE', get_option('windows_noconsole'))
# enable audio support (enable audio forwarding with --forward-audio)
conf.set('AUDIO_SUPPORT', get_option('audio_support'))
configure_file(configuration: conf, output: 'config.h')
src_dir = include_directories('src')
if get_option('windows_noconsole')
c_args = [ '-mwindows' ]
link_args = [ '-mwindows' ]
else
c_args = []
link_args = []
endif
executable('scrcpy', src, dependencies: dependencies, include_directories: src_dir, install: true, c_args: c_args, link_args: link_args)
executable('scrcpy', src, dependencies: dependencies, include_directories: src_dir, install: true)
### TESTS
tests = [
['test_control_event_queue', ['tests/test_control_event_queue.c', 'src/control_event.c']],
['test_control_event_serialize', ['tests/test_control_event_serialize.c', 'src/control_event.c']],
['test_strutil', ['tests/test_strutil.c', 'src/str_util.c']],
['test_control_event_queue', ['tests/test_control_event_queue.c', 'src/controlevent.c']],
['test_control_event_serialize', ['tests/test_control_event_serialize.c', 'src/controlevent.c']],
['test_strutil', ['tests/test_strutil.c', 'src/strutil.c']],
]
foreach t : tests

View File

@@ -777,6 +777,9 @@ enum android_input_source {
AINPUT_SOURCE_JOYSTICK = 0x01000000 | AINPUT_SOURCE_CLASS_JOYSTICK,
/** rotary encoder */
AINPUT_SOURCE_ROTARY_ENCODER = 0x00400000 | AINPUT_SOURCE_CLASS_NONE,
/** any */
AINPUT_SOURCE_ANY = 0xffffff00,
};
/**

206
app/src/aoa.c Normal file
View File

@@ -0,0 +1,206 @@
#include "aoa.h"
#include "command.h" // must be first to include "winsock2.h" before "windows.h"
#include <libusb-1.0/libusb.h>
#include "log.h"
// <https://source.android.com/devices/accessories/aoa2>
#define AOA_GET_PROTOCOL 51
#define AOA_START_ACCESSORY 53
#define AOA_SET_AUDIO_MODE 58
#define AUDIO_MODE_NO_AUDIO 0
#define AUDIO_MODE_S16LSB_STEREO_44100HZ 1
#define DEFAULT_TIMEOUT 1000
typedef struct control_params {
uint8_t request_type;
uint8_t request;
uint16_t value;
uint16_t index;
unsigned char *data;
uint16_t length;
unsigned int timeout;
} control_params;
static void log_libusb_error(enum libusb_error errcode) {
LOGE("%s", libusb_strerror(errcode));
}
static SDL_bool control_transfer(libusb_device_handle *handle, control_params *params) {
int r = libusb_control_transfer(handle,
params->request_type,
params->request,
params->value,
params->index,
params->data,
params->length,
params->timeout);
if (r < 0) {
log_libusb_error(r);
return SDL_FALSE;
}
return SDL_TRUE;
}
static SDL_bool get_serial(libusb_device *device, struct libusb_device_descriptor *desc, unsigned char *data, int length) {
libusb_device_handle *handle;
int r;
if ((r = libusb_open(device, &handle))) {
// silently ignore
LOGD("USB: cannot open device %04x:%04x (%s)", desc->idVendor, desc->idProduct, libusb_strerror(r));
return SDL_FALSE;
}
if (!desc->iSerialNumber) {
LOGD("USB: device %04x:%04x has no serial number available", desc->idVendor, desc->idProduct);
libusb_close(handle);
return SDL_FALSE;
}
if ((r = libusb_get_string_descriptor_ascii(handle, desc->iSerialNumber, data, length)) <= 0) {
// silently ignore
LOGD("USB: cannot read serial of device %04x:%04x (%s)", desc->idVendor, desc->idProduct, libusb_strerror(r));
libusb_close(handle);
return SDL_FALSE;
}
data[length - 1] = '\0'; // just in case
libusb_close(handle);
return SDL_TRUE;
}
static libusb_device *find_device(const char *serial) {
libusb_device **list;
libusb_device *found = NULL;
ssize_t cnt = libusb_get_device_list(NULL, &list);
ssize_t i = 0;
if (cnt < 0) {
log_libusb_error(cnt);
return NULL;
}
for (i = 0; i < cnt; ++i) {
libusb_device *device = list[i];
struct libusb_device_descriptor desc;
libusb_get_device_descriptor(device, &desc);
char usb_serial[128];
if (get_serial(device, &desc, (unsigned char *) usb_serial, sizeof(usb_serial))) {
if (!strncmp(serial, usb_serial, sizeof(usb_serial))) {
libusb_ref_device(device);
found = device;
LOGD("USB device with serial %s found: %04x:%04x", serial, desc.idVendor, desc.idProduct);
break;
}
}
}
libusb_free_device_list(list, 1);
return found;
}
static SDL_bool aoa_get_protocol(libusb_device_handle *handle, uint16_t *version) {
unsigned char data[2];
control_params params = {
.request_type = LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR,
.request = AOA_GET_PROTOCOL,
.value = 0,
.index = 0,
.data = data,
.length = sizeof(data),
.timeout = DEFAULT_TIMEOUT
};
if (control_transfer(handle, &params)) {
// little endian
*version = (data[1] << 8) | data[0];
return SDL_TRUE;
}
return SDL_FALSE;
}
static SDL_bool set_audio_mode(libusb_device_handle *handle, uint16_t mode) {
control_params params = {
.request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR,
.request = AOA_SET_AUDIO_MODE,
// <https://source.android.com/devices/accessories/aoa2.html#audio-support>
.value = mode,
.index = 0, // unused
.data = NULL,
.length = 0,
.timeout = DEFAULT_TIMEOUT
};
return control_transfer(handle, &params);
}
static SDL_bool start_accessory(libusb_device_handle *handle) {
control_params params = {
.request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR,
.request = AOA_START_ACCESSORY,
.value = 0, // unused
.index = 0, // unused
.data = NULL,
.length = 0,
.timeout = DEFAULT_TIMEOUT
};
return control_transfer(handle, &params);
}
SDL_bool aoa_init(void) {
return !libusb_init(NULL);
}
void aoa_exit(void) {
libusb_exit(NULL);
}
SDL_bool aoa_forward_audio(const char *serial, SDL_bool forward) {
LOGD("%s audio accessory...", forward ? "Enabling" : "Disabling");
libusb_device *device = find_device(serial);
if (!device) {
LOGE("Cannot find USB device having serial %s", serial);
return SDL_FALSE;
}
SDL_bool ret = SDL_FALSE;
libusb_device_handle *handle;
int r = libusb_open(device, &handle);
if (r) {
log_libusb_error(r);
goto finally_unref_device;
}
uint16_t version;
if (!aoa_get_protocol(handle, &version)) {
LOGE("Cannot get AOA protocol version");
goto finally_close_handle;
}
LOGD("Device AOA version: %" PRIu16 "\n", version);
if (version < 2) {
LOGE("Device does not support AOA 2: %" PRIu16, version);
goto finally_close_handle;
}
uint16_t mode = forward ? AUDIO_MODE_S16LSB_STEREO_44100HZ : AUDIO_MODE_NO_AUDIO;
if (!set_audio_mode(handle, mode)) {
LOGE("Cannot set audio mode: %" PRIu16, mode);
goto finally_close_handle;
}
if (!start_accessory(handle)) {
LOGE("Cannot start accessory");
return SDL_FALSE;
}
ret = SDL_TRUE;
finally_close_handle:
libusb_close(handle);
finally_unref_device:
libusb_unref_device(device);
return ret;
}

15
app/src/aoa.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef AOA_H
#define AOA_H
#include <SDL2/SDL_stdinc.h>
#define AUDIO_MODE_NO_AUDIO 0
#define AUDIO_MODE_S16LSB_STEREO_44100HZ 1
SDL_bool aoa_init(void);
void aoa_exit(void);
// serial must not be NULL
SDL_bool aoa_forward_audio(const char *serial, SDL_bool forward);
#endif

205
app/src/audio.c Normal file
View File

@@ -0,0 +1,205 @@
#include "audio.h"
#include <SDL2/SDL.h>
#include "aoa.h"
#include "command.h"
#include "log.h"
SDL_bool sdl_audio_init(void) {
if (SDL_InitSubSystem(SDL_INIT_AUDIO)) {
LOGC("Could not initialize SDL audio: %s", SDL_GetError());
return SDL_FALSE;
}
return SDL_TRUE;
}
static void init_audio_spec(SDL_AudioSpec *spec) {
SDL_zero(*spec);
spec->freq = 44100;
spec->format = AUDIO_S16LSB;
spec->channels = 2;
spec->samples = 1024;
}
SDL_bool audio_player_init(struct audio_player *player, const char *serial) {
player->serial = SDL_strdup(serial);
return !!player->serial;
}
void audio_player_destroy(struct audio_player *player) {
SDL_free((void *) player->serial);
}
static void audio_input_callback(void *userdata, Uint8 *stream, int len) {
struct audio_player *player = userdata;
if (SDL_QueueAudio(player->output_device, stream, len)) {
LOGE("Cannot queue audio: %s", SDL_GetError());
}
}
static int get_matching_audio_device(const char *serial, int count) {
for (int i = 0; i < count; ++i) {
LOGD("Audio input #%d: %s", i, SDL_GetAudioDeviceName(i, 1));
}
char model[128];
int r = adb_read_model(serial, model, sizeof(model));
if (r <= 0) {
LOGE("Cannot read Android device model");
return -1;
}
LOGD("Device model is: %s", model);
// iterate backwards since the matching device is probably the last one
for (int i = count - 1; i >= 0; i--) {
// model is a NUL-terminated string
const char *name = SDL_GetAudioDeviceName(i, 1);
if (strstr(name, model)) {
// the device name contains the device model, we found it!
return i;
}
}
return -1;
}
static SDL_AudioDeviceID open_accessory_audio_input(struct audio_player *player) {
int count = SDL_GetNumAudioDevices(1);
if (!count) {
LOGE("No audio input source found");
return 0;
}
int selected = get_matching_audio_device(player->serial, count);
if (selected == -1) {
LOGE("Cannot find the Android accessory audio input source");
return 0;
}
const char *selected_name = SDL_GetAudioDeviceName(selected, 1);
LOGI("Selecting audio input source: %s", selected_name);
SDL_AudioSpec spec;
init_audio_spec(&spec);
spec.callback = audio_input_callback;
spec.userdata = player;
int id = SDL_OpenAudioDevice(selected_name, 1, &spec, NULL, 0);
if (!id) {
LOGE("Cannot open audio input: %s", SDL_GetError());
}
return id;
}
static SDL_AudioDeviceID open_default_audio_output() {
SDL_AudioSpec spec;
init_audio_spec(&spec);
int id = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0);
if (!id) {
LOGE("Cannot open audio output: %s", SDL_GetError());
}
return id;
}
SDL_bool audio_player_open(struct audio_player *player) {
player->output_device = open_default_audio_output();
if (!player->output_device) {
return SDL_FALSE;
}
player->input_device = open_accessory_audio_input(player);
if (!player->input_device) {
SDL_CloseAudioDevice(player->output_device);
return SDL_FALSE;
}
return SDL_TRUE;
}
static void audio_player_set_paused(struct audio_player *player, SDL_bool paused) {
SDL_PauseAudioDevice(player->input_device, paused);
SDL_PauseAudioDevice(player->output_device, paused);
}
void audio_player_play(struct audio_player *player) {
audio_player_set_paused(player, SDL_FALSE);
}
void audio_player_pause(struct audio_player *player) {
audio_player_set_paused(player, SDL_TRUE);
}
void audio_player_close(struct audio_player *player) {
SDL_CloseAudioDevice(player->input_device);
SDL_CloseAudioDevice(player->output_device);
}
SDL_bool audio_forwarding_start(struct audio_player *player, const char *serial) {
if (!aoa_init()) {
LOGE("Cannot initialize AOA");
return SDL_FALSE;
}
char serialno[128];
if (!serial) {
LOGD("No serial provided, request it to the device");
int r = adb_read_serialno(NULL, serialno, sizeof(serialno));
if (r <= 0) {
LOGE("Cannot read serial from the device");
goto error_aoa_exit;
}
LOGD("Device serial is %s", serialno);
serial = serialno;
}
if (!audio_player_init(player, serial)) {
LOGE("Cannot initialize audio player");
goto error_aoa_exit;
}
// adb connection will be reset!
if (!aoa_forward_audio(player->serial, SDL_TRUE)) {
LOGE("AOA audio forwarding failed");
goto error_destroy_player;
}
LOGI("Audio accessory enabled");
if (!sdl_audio_init()) {
goto error_disable_audio_forwarding;
}
LOGI("Waiting 2s for USB reconfiguration...");
SDL_Delay(2000);
if (!audio_player_open(player)) {
goto error_disable_audio_forwarding;
}
audio_player_play(player);
return SDL_TRUE;
error_disable_audio_forwarding:
if (!aoa_forward_audio(serial, SDL_FALSE)) {
LOGW("Cannot disable audio forwarding");
}
error_destroy_player:
audio_player_destroy(player);
error_aoa_exit:
aoa_exit();
return SDL_FALSE;
}
void audio_forwarding_stop(struct audio_player *player) {
audio_player_close(player);
if (aoa_forward_audio(player->serial, SDL_FALSE)) {
LOGI("Audio forwarding disabled");
} else {
LOGW("Cannot disable audio forwarding");
}
aoa_exit();
audio_player_destroy(player);
}

29
app/src/audio.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef AUDIO_H
#define AUDIO_H
#include <SDL2/SDL_audio.h>
#include <SDL2/SDL_stdinc.h>
struct audio_player {
const char *serial;
SDL_AudioDeviceID input_device;
SDL_AudioDeviceID output_device;
};
SDL_bool sdl_audio_init(void);
// serial must not be NULL
SDL_bool audio_player_init(struct audio_player *player, const char *serial);
void audio_player_destroy(struct audio_player *player);
SDL_bool audio_player_open(struct audio_player *player);
void audio_player_close(struct audio_player *player);
void audio_player_play(struct audio_player *player);
void audio_player_pause(struct audio_player *player);
// for convenience, these functions handle everything
SDL_bool audio_forwarding_start(struct audio_player *player, const char *serial);
void audio_forwarding_stop(struct audio_player *player);
#endif

View File

@@ -6,11 +6,10 @@
#include "common.h"
#include "log.h"
#include "str_util.h"
static const char *adb_command;
static inline const char *get_adb_command(void) {
static inline const char *get_adb_command() {
if (!adb_command) {
adb_command = getenv("ADB");
if (!adb_command)
@@ -19,25 +18,8 @@ static inline const char *get_adb_command(void) {
return adb_command;
}
static void show_adb_err_msg(enum process_result err) {
switch (err) {
case PROCESS_ERROR_GENERIC:
LOGE("Failed to execute adb");
break;
case PROCESS_ERROR_MISSING_BINARY:
LOGE("'adb' command not found (make it accessible from your PATH "
"or define its full path in the ADB environment variable)");
break;
case PROCESS_SUCCESS:
/* do nothing */
break;
}
}
process_t adb_execute(const char *serial, const char *const adb_cmd[], int len) {
const char *cmd[len + 4];
static void fill_cmd(const char *cmd[], const char *serial, const char *const adb_cmd[], int len) {
int i;
process_t process;
cmd[0] = get_adb_command();
if (serial) {
cmd[1] = "-s";
@@ -49,12 +31,19 @@ process_t adb_execute(const char *serial, const char *const adb_cmd[], int len)
memcpy(&cmd[i], adb_cmd, len * sizeof(const char *));
cmd[len + i] = NULL;
enum process_result r = cmd_execute(cmd[0], cmd, &process);
if (r != PROCESS_SUCCESS) {
show_adb_err_msg(r);
return PROCESS_NONE;
}
return process;
}
process_t adb_execute(const char *serial, const char *const adb_cmd[], int len) {
const char *cmd[len + 4];
fill_cmd(cmd, serial, adb_cmd, len);
return cmd_execute(cmd[0], cmd);
}
process_t adb_execute_redirect(const char *serial, const char *const adb_cmd[], int len,
pipe_t *pipe_stdin, pipe_t *pipe_stdout, pipe_t *pipe_stderr) {
const char *cmd[len + 4];
fill_cmd(cmd, serial, adb_cmd, len);
return cmd_execute_redirect(cmd[0], cmd, pipe_stdin, pipe_stdout, pipe_stderr);
}
process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name) {
@@ -90,49 +79,8 @@ process_t adb_reverse_remove(const char *serial, const char *device_socket_name)
}
process_t adb_push(const char *serial, const char *local, const char *remote) {
#ifdef __WINDOWS__
// Windows will parse the string, so the paths must be quoted
// (see sys/win/command.c)
local = strquote(local);
if (!local) {
return PROCESS_NONE;
}
remote = strquote(remote);
if (!remote) {
free((void *) local);
return PROCESS_NONE;
}
#endif
const char *const adb_cmd[] = {"push", local, remote};
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__
free((void *) remote);
free((void *) local);
#endif
return proc;
}
process_t adb_install(const char *serial, const char *local) {
#ifdef __WINDOWS__
// Windows will parse the string, so the local name must be quoted
// (see sys/win/command.c)
local = strquote(local);
if (!local) {
return PROCESS_NONE;
}
#endif
const char *const adb_cmd[] = {"install", "-r", local};
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__
free((void *) local);
#endif
return proc;
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
process_t adb_remove_path(const char *serial, const char *path) {
@@ -140,6 +88,40 @@ process_t adb_remove_path(const char *serial, const char *path) {
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
static int adb_execute_get_output(const char *serial, const char *const adb_cmd[], int adb_cmd_len,
char *data, size_t data_len, const char *name) {
pipe_t pipe_stdout;
process_t proc = adb_execute_redirect(serial, adb_cmd, adb_cmd_len, NULL, &pipe_stdout, NULL);
if (!process_check_success(proc, name)) {
return -1;
}
int r = read_pipe(pipe_stdout, data, data_len);
close_pipe(pipe_stdout);
return r;
}
static int truncate_first_line(char *data, int len) {
data[len - 1] = '\0';
char *eol = strpbrk(data, "\r\n");
if (eol) {
*eol = '\0';
len = eol - data;
}
return len;
}
int adb_read_serialno(const char *serial, char *data, size_t len) {
const char *const adb_cmd[] = {"get-serialno"};
int r = adb_execute_get_output(serial, adb_cmd, ARRAY_LEN(adb_cmd), data, len, "get-serialno");
return r <= 0 ? r : truncate_first_line(data, r);
}
int adb_read_model(const char *serial, char *data, size_t len) {
const char *const adb_cmd[] = {"shell", "getprop", "ro.product.model"};
int r = adb_execute_get_output(serial, adb_cmd, ARRAY_LEN(adb_cmd), data, len, "getprop model");
return r <= 0 ? r : truncate_first_line(data, r);
}
SDL_bool process_check_success(process_t proc, const char *name) {
if (proc == PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name);

View File

@@ -24,35 +24,42 @@
# define PROCESS_NONE NULL
typedef HANDLE process_t;
typedef DWORD exit_code_t;
typedef HANDLE pipe_t;
#else
# include <sys/types.h>
# define PROCESS_NONE -1
typedef pid_t process_t;
typedef int exit_code_t;
typedef int pipe_t;
#endif
# define NO_EXIT_CODE -1
enum process_result {
PROCESS_SUCCESS,
PROCESS_ERROR_GENERIC,
PROCESS_ERROR_MISSING_BINARY,
};
enum process_result cmd_execute(const char *path, const char *const argv[], process_t *process);
process_t cmd_execute(const char *path, const char *const argv[]);
process_t cmd_execute_redirect(const char *path, const char *const argv[],
pipe_t *pipe_stdin, pipe_t *pipe_stdout, pipe_t *pipe_stderr);
SDL_bool cmd_terminate(process_t pid);
SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code);
process_t adb_execute(const char *serial, const char *const adb_cmd[], int len);
process_t adb_execute_redirect(const char *serial, const char *const adb_cmd[], int len,
pipe_t *pipe_stdin, pipe_t *pipe_stdout, pipe_t *pipe_stderr);
process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name);
process_t adb_forward_remove(const char *serial, uint16_t local_port);
process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port);
process_t adb_reverse_remove(const char *serial, const char *device_socket_name);
process_t adb_push(const char *serial, const char *local, const char *remote);
process_t adb_install(const char *serial, const char *local);
process_t adb_remove_path(const char *serial, const char *path);
// return number of bytes read (-1 on error)
int adb_read_serialno(const char *serial, char *data, size_t len);
int adb_read_model(const char *serial, char *data, size_t len);
// convenience function to wait for a successful process execution
// automatically log process errors with the provided process name
SDL_bool process_check_success(process_t process, const char *name);
int read_pipe(pipe_t pipe, char *data, size_t len);
void close_pipe(pipe_t pipe);
#endif

View File

@@ -1,9 +1,8 @@
#include "control_event.h"
#include "controlevent.h"
#include <SDL2/SDL_stdinc.h>
#include <string.h>
#include "lock_util.h"
#include "lockutil.h"
#include "log.h"
static inline void write16(Uint8 *buf, Uint16 value) {

View File

@@ -1,8 +1,6 @@
#include "controller.h"
#include <SDL2/SDL_assert.h>
#include "config.h"
#include "lock_util.h"
#include "lockutil.h"
#include "log.h"
SDL_bool controller_init(struct controller *controller, socket_t video_socket) {
@@ -56,28 +54,27 @@ static SDL_bool process_event(struct controller *controller, const struct contro
static int run_controller(void *data) {
struct controller *controller = data;
mutex_lock(controller->mutex);
for (;;) {
mutex_lock(controller->mutex);
while (!controller->stopped && control_event_queue_is_empty(&controller->queue)) {
cond_wait(controller->event_cond, controller->mutex);
}
if (controller->stopped) {
// stop immediately, do not process further events
mutex_unlock(controller->mutex);
break;
}
struct control_event event;
SDL_bool non_empty = control_event_queue_take(&controller->queue, &event);
SDL_assert(non_empty);
mutex_unlock(controller->mutex);
SDL_bool ok = process_event(controller, &event);
control_event_destroy(&event);
if (!ok) {
LOGD("Cannot write event to socket");
break;
while (control_event_queue_take(&controller->queue, &event)) {
SDL_bool ok = process_event(controller, &event);
control_event_destroy(&event);
if (!ok) {
LOGD("Cannot write event to socket");
goto end;
}
}
}
end:
mutex_unlock(controller->mutex);
return 0;
}

View File

@@ -1,7 +1,7 @@
#ifndef CONTROL_H
#define CONTROL_H
#include "control_event.h"
#include "controlevent.h"
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>

View File

@@ -70,7 +70,7 @@ static enum android_metastate convert_meta_state(SDL_Keymod mod) {
return autocomplete_metastate(metastate);
}
static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to, Uint16 mod) {
static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to) {
switch (from) {
MAP(SDLK_RETURN, AKEYCODE_ENTER);
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
@@ -86,39 +86,6 @@ static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to, Uint
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
}
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
return SDL_FALSE;
}
// if ALT and META are not pressed, also handle letters and space
switch (from) {
MAP(SDLK_a, AKEYCODE_A);
MAP(SDLK_b, AKEYCODE_B);
MAP(SDLK_c, AKEYCODE_C);
MAP(SDLK_d, AKEYCODE_D);
MAP(SDLK_e, AKEYCODE_E);
MAP(SDLK_f, AKEYCODE_F);
MAP(SDLK_g, AKEYCODE_G);
MAP(SDLK_h, AKEYCODE_H);
MAP(SDLK_i, AKEYCODE_I);
MAP(SDLK_j, AKEYCODE_J);
MAP(SDLK_k, AKEYCODE_K);
MAP(SDLK_l, AKEYCODE_L);
MAP(SDLK_m, AKEYCODE_M);
MAP(SDLK_n, AKEYCODE_N);
MAP(SDLK_o, AKEYCODE_O);
MAP(SDLK_p, AKEYCODE_P);
MAP(SDLK_q, AKEYCODE_Q);
MAP(SDLK_r, AKEYCODE_R);
MAP(SDLK_s, AKEYCODE_S);
MAP(SDLK_t, AKEYCODE_T);
MAP(SDLK_u, AKEYCODE_U);
MAP(SDLK_v, AKEYCODE_V);
MAP(SDLK_w, AKEYCODE_W);
MAP(SDLK_x, AKEYCODE_X);
MAP(SDLK_y, AKEYCODE_Y);
MAP(SDLK_z, AKEYCODE_Z);
MAP(SDLK_SPACE, AKEYCODE_SPACE);
FAIL;
}
}
@@ -159,12 +126,11 @@ SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
return SDL_FALSE;
}
Uint16 mod = from->keysym.mod;
if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode, mod)) {
if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode)) {
return SDL_FALSE;
}
to->keycode_event.metastate = convert_meta_state(mod);
to->keycode_event.metastate = convert_meta_state(from->keysym.mod);
return SDL_TRUE;
}

View File

@@ -3,7 +3,7 @@
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_events.h>
#include "control_event.h"
#include "controlevent.h"
struct complete_mouse_motion_event {
SDL_MouseMotionEvent *mouse_motion_event;

View File

@@ -9,7 +9,7 @@
#include "config.h"
#include "events.h"
#include "frames.h"
#include "lock_util.h"
#include "lockutil.h"
#include "log.h"
#define BUFSIZE 0x10000

View File

@@ -7,7 +7,6 @@
#include "net.h"
#define DEVICE_NAME_FIELD_LENGTH 64
#define DEVICE_SDCARD_PATH "/sdcard/"
// name must be at least DEVICE_NAME_FIELD_LENGTH bytes
SDL_bool device_read_info(socket_t device_socket, char *name, struct size *frame_size);

View File

@@ -1,231 +0,0 @@
#include "file_handler.h"
#include <string.h>
#include <SDL2/SDL_assert.h>
#include "config.h"
#include "command.h"
#include "device.h"
#include "lock_util.h"
#include "log.h"
struct request {
file_handler_action_t action;
const char *file;
};
static struct request *request_new(file_handler_action_t action, const char *file) {
struct request *req = SDL_malloc(sizeof(*req));
if (!req) {
return NULL;
}
req->action = action;
req->file = file;
return req;
}
static void request_free(struct request *req) {
if (!req) {
return;
}
SDL_free((void *) req->file);
SDL_free((void *) req);
}
static SDL_bool request_queue_is_empty(const struct request_queue *queue) {
return queue->head == queue->tail;
}
static SDL_bool request_queue_is_full(const struct request_queue *queue) {
return (queue->head + 1) % REQUEST_QUEUE_SIZE == queue->tail;
}
static SDL_bool request_queue_init(struct request_queue *queue) {
queue->head = 0;
queue->tail = 0;
return SDL_TRUE;
}
static void request_queue_destroy(struct request_queue *queue) {
int i = queue->tail;
while (i != queue->head) {
request_free(queue->reqs[i]);
i = (i + 1) % REQUEST_QUEUE_SIZE;
}
}
static SDL_bool request_queue_push(struct request_queue *queue, struct request *req) {
if (request_queue_is_full(queue)) {
return SDL_FALSE;
}
queue->reqs[queue->head] = req;
queue->head = (queue->head + 1) % REQUEST_QUEUE_SIZE;
return SDL_TRUE;
}
static SDL_bool request_queue_take(struct request_queue *queue, struct request **req) {
if (request_queue_is_empty(queue)) {
return SDL_FALSE;
}
// transfer ownership
*req = queue->reqs[queue->tail];
queue->tail = (queue->tail + 1) % REQUEST_QUEUE_SIZE;
return SDL_TRUE;
}
SDL_bool file_handler_init(struct file_handler *file_handler, const char *serial) {
if (!request_queue_init(&file_handler->queue)) {
return SDL_FALSE;
}
if (!(file_handler->mutex = SDL_CreateMutex())) {
return SDL_FALSE;
}
if (!(file_handler->event_cond = SDL_CreateCond())) {
SDL_DestroyMutex(file_handler->mutex);
return SDL_FALSE;
}
if (serial) {
file_handler->serial = SDL_strdup(serial);
if (!file_handler->serial) {
LOGW("Cannot strdup serial");
SDL_DestroyMutex(file_handler->mutex);
return SDL_FALSE;
}
} else {
file_handler->serial = NULL;
}
// lazy initialization
file_handler->initialized = SDL_FALSE;
file_handler->stopped = SDL_FALSE;
file_handler->current_process = PROCESS_NONE;
return SDL_TRUE;
}
void file_handler_destroy(struct file_handler *file_handler) {
SDL_DestroyCond(file_handler->event_cond);
SDL_DestroyMutex(file_handler->mutex);
request_queue_destroy(&file_handler->queue);
SDL_free((void *) file_handler->serial);
}
static process_t install_apk(const char *serial, const char *file) {
return adb_install(serial, file);
}
static process_t push_file(const char *serial, const char *file) {
return adb_push(serial, file, DEVICE_SDCARD_PATH);
}
SDL_bool file_handler_request(struct file_handler *file_handler,
file_handler_action_t action,
const char *file) {
SDL_bool res;
// start file_handler if it's used for the first time
if (!file_handler->initialized) {
if (!file_handler_start(file_handler)) {
return SDL_FALSE;
}
file_handler->initialized = SDL_TRUE;
}
LOGI("Request to %s %s", action == ACTION_INSTALL_APK ? "install" : "push", file);
struct request *req = request_new(action, file);
if (!req) {
LOGE("Could not create request");
return SDL_FALSE;
}
mutex_lock(file_handler->mutex);
SDL_bool was_empty = request_queue_is_empty(&file_handler->queue);
res = request_queue_push(&file_handler->queue, req);
if (was_empty) {
cond_signal(file_handler->event_cond);
}
mutex_unlock(file_handler->mutex);
return res;
}
static int run_file_handler(void *data) {
struct file_handler *file_handler = data;
for (;;) {
mutex_lock(file_handler->mutex);
file_handler->current_process = PROCESS_NONE;
while (!file_handler->stopped && request_queue_is_empty(&file_handler->queue)) {
cond_wait(file_handler->event_cond, file_handler->mutex);
}
if (file_handler->stopped) {
// stop immediately, do not process further events
mutex_unlock(file_handler->mutex);
break;
}
struct request *req;
SDL_bool non_empty = request_queue_take(&file_handler->queue, &req);
SDL_assert(non_empty);
process_t process;
if (req->action == ACTION_INSTALL_APK) {
LOGI("Installing %s...", req->file);
process = install_apk(file_handler->serial, req->file);
} else {
LOGI("Pushing %s...", req->file);
process = push_file(file_handler->serial, req->file);
}
file_handler->current_process = process;
mutex_unlock(file_handler->mutex);
if (req->action == ACTION_INSTALL_APK) {
if (process_check_success(process, "adb install")) {
LOGI("%s successfully installed", req->file);
} else {
LOGE("Failed to install %s", req->file);
}
} else {
if (process_check_success(process, "adb push")) {
LOGI("%s successfully pushed to /sdcard/", req->file);
} else {
LOGE("Failed to push %s to /sdcard/", req->file);
}
}
request_free(req);
}
return 0;
}
SDL_bool file_handler_start(struct file_handler *file_handler) {
LOGD("Starting file_handler thread");
file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler", file_handler);
if (!file_handler->thread) {
LOGC("Could not start file_handler thread");
return SDL_FALSE;
}
return SDL_TRUE;
}
void file_handler_stop(struct file_handler *file_handler) {
mutex_lock(file_handler->mutex);
file_handler->stopped = SDL_TRUE;
cond_signal(file_handler->event_cond);
if (file_handler->current_process != PROCESS_NONE) {
if (!cmd_terminate(file_handler->current_process)) {
LOGW("Cannot terminate install process");
}
cmd_simple_wait(file_handler->current_process, NULL);
file_handler->current_process = PROCESS_NONE;
}
mutex_unlock(file_handler->mutex);
}
void file_handler_join(struct file_handler *file_handler) {
SDL_WaitThread(file_handler->thread, NULL);
}

View File

@@ -1,44 +0,0 @@
#ifndef FILE_HANDLER_H
#define FILE_HANDLER_H
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_thread.h>
#include "command.h"
#define REQUEST_QUEUE_SIZE 16
typedef enum {
ACTION_INSTALL_APK,
ACTION_PUSH_FILE,
} file_handler_action_t;
struct request_queue {
struct request *reqs[REQUEST_QUEUE_SIZE];
int tail;
int head;
};
struct file_handler {
const char *serial;
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *event_cond;
SDL_bool stopped;
SDL_bool initialized;
process_t current_process;
struct request_queue queue;
};
SDL_bool file_handler_init(struct file_handler *file_handler, const char *serial);
void file_handler_destroy(struct file_handler *file_handler);
SDL_bool file_handler_start(struct file_handler *file_handler);
void file_handler_stop(struct file_handler *file_handler);
void file_handler_join(struct file_handler *file_handler);
SDL_bool file_handler_request(struct file_handler *file_handler,
file_handler_action_t action,
const char *file);
#endif

View File

@@ -1,4 +1,4 @@
#include "fps_counter.h"
#include "fpscounter.h"
#include <SDL2/SDL_timer.h>

View File

@@ -6,7 +6,7 @@
#include <libavformat/avformat.h>
#include "config.h"
#include "lock_util.h"
#include "lockutil.h"
#include "log.h"
SDL_bool frames_init(struct frames *frames) {

View File

@@ -5,7 +5,7 @@
#include <SDL2/SDL_stdinc.h>
#include "config.h"
#include "fps_counter.h"
#include "fpscounter.h"
// forward declarations
typedef struct AVFrame AVFrame;

View File

@@ -1,8 +1,7 @@
#include "input_manager.h"
#include "inputmanager.h"
#include <SDL2/SDL_assert.h>
#include "convert.h"
#include "lock_util.h"
#include "lockutil.h"
#include "log.h"
// Convert window coordinates (as provided by SDL_GetMouseState() to renderer coordinates (as provided in SDL mouse events)
@@ -30,58 +29,52 @@ static struct point get_mouse_point(struct screen *screen) {
};
}
static const int ACTION_DOWN = 1;
static const int ACTION_UP = 1 << 1;
static void send_keycode(struct controller *controller, enum android_keycode keycode, int actions, const char *name) {
static void send_keycode(struct controller *controller, enum android_keycode keycode, const char *name) {
// send DOWN event
struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_KEYCODE;
control_event.keycode_event.action = AKEY_EVENT_ACTION_DOWN;
control_event.keycode_event.keycode = keycode;
control_event.keycode_event.metastate = 0;
if (actions & ACTION_DOWN) {
control_event.keycode_event.action = AKEY_EVENT_ACTION_DOWN;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot send %s (DOWN)", name);
return;
}
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot send %s (DOWN)", name);
return;
}
if (actions & ACTION_UP) {
control_event.keycode_event.action = AKEY_EVENT_ACTION_UP;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot send %s (UP)", name);
}
// send UP event
control_event.keycode_event.action = AKEY_EVENT_ACTION_UP;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot send %s (UP)", name);
}
}
static inline void action_home(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_HOME, actions, "HOME");
static inline void action_home(struct controller *controller) {
send_keycode(controller, AKEYCODE_HOME, "HOME");
}
static inline void action_back(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_BACK, actions, "BACK");
static inline void action_back(struct controller *controller) {
send_keycode(controller, AKEYCODE_BACK, "BACK");
}
static inline void action_app_switch(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_APP_SWITCH, actions, "APP_SWITCH");
static inline void action_app_switch(struct controller *controller) {
send_keycode(controller, AKEYCODE_APP_SWITCH, "APP_SWITCH");
}
static inline void action_power(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_POWER, actions, "POWER");
static inline void action_power(struct controller *controller) {
send_keycode(controller, AKEYCODE_POWER, "POWER");
}
static inline void action_volume_up(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_VOLUME_UP, actions, "VOLUME_UP");
static inline void action_volume_up(struct controller *controller) {
send_keycode(controller, AKEYCODE_VOLUME_UP, "VOLUME_UP");
}
static inline void action_volume_down(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, actions, "VOLUME_DOWN");
static inline void action_volume_down(struct controller *controller) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, "VOLUME_DOWN");
}
static inline void action_menu(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
static inline void action_menu(struct controller *controller) {
send_keycode(controller, AKEYCODE_MENU, "MENU");
}
// turn the screen on if it was off, press BACK otherwise
@@ -130,12 +123,6 @@ static void clipboard_paste(struct controller *controller) {
void input_manager_process_text_input(struct input_manager *input_manager,
const SDL_TextInputEvent *event) {
char c = event->text[0];
if (isalpha(c) || c == ' ') {
SDL_assert(event->text[1] == '\0');
// letters and space are handled as raw key event
return;
}
struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_TEXT;
control_event.text_event.text = SDL_strdup(event->text);
@@ -151,97 +138,60 @@ void input_manager_process_text_input(struct input_manager *input_manager,
void input_manager_process_key(struct input_manager *input_manager,
const SDL_KeyboardEvent *event) {
SDL_bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
SDL_bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
SDL_bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
if (alt) {
// no shortcut involves Alt or Meta, and they should not be forwarded
// to the device
return;
}
// capture all Ctrl events
if (ctrl | meta) {
if (ctrl) {
SDL_bool repeat = event->repeat;
// only consider keydown events, and ignore repeated events
if (repeat || event->type != SDL_KEYDOWN) {
return;
}
SDL_bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
if (shift) {
// currently, there is no shortcut involving SHIFT
// currently, there is no shortcut implying SHIFT
return;
}
SDL_Keycode keycode = event->keysym.sym;
int action = event->type == SDL_KEYDOWN ? ACTION_DOWN : ACTION_UP;
SDL_bool repeat = event->repeat;
switch (keycode) {
case SDLK_h:
if (ctrl && !meta && !repeat) {
action_home(input_manager->controller, action);
}
action_home(input_manager->controller);
return;
case SDLK_b: // fall-through
case SDLK_BACKSPACE:
if (ctrl && !meta && !repeat) {
action_back(input_manager->controller, action);
}
action_back(input_manager->controller);
return;
case SDLK_s:
if (ctrl && !meta && !repeat) {
action_app_switch(input_manager->controller, action);
}
action_app_switch(input_manager->controller);
return;
case SDLK_m:
if (ctrl && !meta && !repeat) {
action_menu(input_manager->controller, action);
}
action_menu(input_manager->controller);
return;
case SDLK_p:
if (ctrl && !meta && !repeat) {
action_power(input_manager->controller, action);
}
action_power(input_manager->controller);
return;
case SDLK_DOWN:
#ifdef __APPLE__
if (!ctrl && meta) {
#else
if (ctrl && !meta) {
#endif
// forward repeated events
action_volume_down(input_manager->controller, action);
}
action_volume_down(input_manager->controller);
return;
case SDLK_UP:
#ifdef __APPLE__
if (!ctrl && meta) {
#else
if (ctrl && !meta) {
#endif
// forward repeated events
action_volume_up(input_manager->controller, action);
}
action_volume_up(input_manager->controller);
return;
case SDLK_v:
if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
clipboard_paste(input_manager->controller);
}
clipboard_paste(input_manager->controller);
return;
case SDLK_f:
if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
screen_switch_fullscreen(input_manager->screen);
}
screen_switch_fullscreen(input_manager->screen);
return;
case SDLK_x:
if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
screen_resize_to_fit(input_manager->screen);
}
screen_resize_to_fit(input_manager->screen);
return;
case SDLK_g:
if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
screen_resize_to_pixel_perfect(input_manager->screen);
}
screen_resize_to_pixel_perfect(input_manager->screen);
return;
case SDLK_i:
if (ctrl && !meta && !repeat && event->type == SDL_KEYDOWN) {
switch_fps_counter_state(input_manager->frames);
}
switch_fps_counter_state(input_manager->frames);
return;
}
@@ -278,7 +228,7 @@ void input_manager_process_mouse_button(struct input_manager *input_manager,
return;
}
if (event->button == SDL_BUTTON_MIDDLE) {
action_home(input_manager->controller, ACTION_DOWN | ACTION_UP);
action_home(input_manager->controller);
return;
}
// double-click on black borders resize to fit the device screen
@@ -292,7 +242,7 @@ void input_manager_process_mouse_button(struct input_manager *input_manager,
}
// otherwise, send the click event to the device
}
}
};
struct control_event control_event;
if (mouse_button_from_sdl_to_android(event, input_manager->screen->frame_size, &control_event)) {
if (!controller_push_event(input_manager->controller, &control_event)) {

View File

@@ -3,7 +3,7 @@
#include "common.h"
#include "controller.h"
#include "fps_counter.h"
#include "fpscounter.h"
#include "frames.h"
#include "screen.h"

View File

@@ -1,4 +1,3 @@
#include <lock_util.h>
#include <stdlib.h>
#include <SDL2/SDL_mutex.h>

View File

@@ -10,11 +10,12 @@
struct args {
const char *serial;
const char *crop;
SDL_bool fullscreen;
SDL_bool help;
SDL_bool version;
SDL_bool show_touches;
#ifdef AUDIO_SUPPORT
SDL_bool forward_audio;
#endif
Uint16 port;
Uint16 max_size;
Uint32 bit_rate;
@@ -25,21 +26,18 @@ static void usage(const char *arg0) {
"Usage: %s [options]\n"
"\n"
"Options:\n"
#ifdef AUDIO_SUPPORT
"\n"
" -a, --forward-audio\n"
" Forward audio from the device to the computer over USB\n"
" (experimental).\n"
#endif
"\n"
" -b, --bit-rate value\n"
" Encode the video at the given bit-rate, expressed in bits/s.\n"
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
" Default is %d.\n"
"\n"
" -c, --crop width:height:x:y\n"
" Crop the device screen on the server.\n"
" The values are expressed in the device natural orientation\n"
" (typically, portrait for a phone, landscape for a tablet).\n"
" Any --max-size value is computed on the cropped size.\n"
"\n"
" -f, --fullscreen\n"
" Start in fullscreen.\n"
"\n"
" -h, --help\n"
" Print this help.\n"
"\n"
@@ -109,9 +107,6 @@ static void usage(const char *arg0) {
"\n"
" Ctrl+i\n"
" enable/disable FPS counter (print frames/second in logs)\n"
"\n"
" Drag & drop APK file\n"
" install APK from computer\n"
"\n",
arg0,
DEFAULT_BIT_RATE,
@@ -202,31 +197,36 @@ static SDL_bool parse_port(char *optarg, Uint16 *port) {
static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
static const struct option long_options[] = {
{"bit-rate", required_argument, NULL, 'b'},
{"crop", required_argument, NULL, 'c'},
{"fullscreen", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"max-size", required_argument, NULL, 'm'},
{"port", required_argument, NULL, 'p'},
{"serial", required_argument, NULL, 's'},
{"show-touches", no_argument, NULL, 't'},
{"version", no_argument, NULL, 'v'},
{NULL, 0, NULL, 0 },
#ifdef AUDIO_SUPPORT
{"forward-audio", no_argument, NULL, 'a'},
#endif
{"bit-rate", required_argument, NULL, 'b'},
{"help", no_argument, NULL, 'h'},
{"max-size", required_argument, NULL, 'm'},
{"port", required_argument, NULL, 'p'},
{"serial", required_argument, NULL, 's'},
{"show-touches", no_argument, NULL, 't'},
{"version", no_argument, NULL, 'v'},
{NULL, 0, NULL, 0 },
};
int c;
while ((c = getopt_long(argc, argv, "b:c:fhm:p:s:tv", long_options, NULL)) != -1) {
#ifdef AUDIO_SUPPORT
# define AUDIO_SHORT_PARAM "a"
#else
# define AUDIO_SHORT_PARAM
#endif
while ((c = getopt_long(argc, argv, AUDIO_SHORT_PARAM "b:hm:p:s:tv", long_options, NULL)) != -1) {
switch (c) {
#ifdef AUDIO_SUPPORT
case 'a':
args->forward_audio = SDL_TRUE;
break;
#endif
case 'b':
if (!parse_bit_rate(optarg, &args->bit_rate)) {
return SDL_FALSE;
}
break;
case 'c':
args->crop = optarg;
break;
case 'f':
args->fullscreen = SDL_TRUE;
break;
case 'h':
args->help = SDL_TRUE;
break;
@@ -272,13 +272,15 @@ int main(int argc, char *argv[]) {
#endif
struct args args = {
.serial = NULL,
.crop = NULL,
.help = SDL_FALSE,
.version = SDL_FALSE,
.show_touches = SDL_FALSE,
.port = DEFAULT_LOCAL_PORT,
.max_size = DEFAULT_MAX_SIZE,
.bit_rate = DEFAULT_BIT_RATE,
#ifdef AUDIO_SUPPORT
.forward_audio = SDL_FALSE,
#endif
};
if (!parse_args(&args, argc, argv)) {
return 1;
@@ -294,9 +296,7 @@ int main(int argc, char *argv[]) {
return 0;
}
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
av_register_all();
#endif
if (avformat_network_init()) {
return 1;
@@ -308,22 +308,19 @@ int main(int argc, char *argv[]) {
struct scrcpy_options options = {
.serial = args.serial,
.crop = args.crop,
.port = args.port,
.max_size = args.max_size,
.bit_rate = args.bit_rate,
.show_touches = args.show_touches,
.fullscreen = args.fullscreen,
#ifdef AUDIO_SUPPORT
.forward_audio = args.forward_audio,
#endif
};
int res = scrcpy(&options) ? 0 : 1;
avformat_network_deinit(); // ignore failure
#if defined (__WINDOWS__) && ! defined (WINDOWS_NOCONSOLE)
if (res != 0) {
fprintf(stderr, "Press any key to continue...\n");
getchar();
}
#endif
SDL_Quit();
return res;
}

View File

@@ -87,7 +87,7 @@ ssize_t net_send(socket_t socket, const void *buf, size_t len) {
}
ssize_t net_send_all(socket_t socket, const void *buf, size_t len) {
ssize_t w = 0;
ssize_t w;
while (len > 0) {
w = send(socket, buf, len, 0);
if (w == -1) {

View File

@@ -7,29 +7,33 @@
#include <sys/time.h>
#include <SDL2/SDL.h>
#include "aoa.h"
#include "audio.h"
#include "command.h"
#include "common.h"
#include "controller.h"
#include "decoder.h"
#include "device.h"
#include "events.h"
#include "file_handler.h"
#include "frames.h"
#include "fps_counter.h"
#include "input_manager.h"
#include "fpscounter.h"
#include "inputmanager.h"
#include "log.h"
#include "lock_util.h"
#include "lockutil.h"
#include "net.h"
#include "screen.h"
#include "server.h"
#include "tiny_xpm.h"
#include "tinyxpm.h"
static struct server server = SERVER_INITIALIZER;
static struct screen screen = SCREEN_INITIALIZER;
static struct frames frames;
static struct decoder decoder;
static struct controller controller;
static struct file_handler file_handler;
#ifdef AUDIO_SUPPORT
static struct audio_player audio_player;
#endif
static struct input_manager input_manager = {
.controller = &controller,
@@ -56,12 +60,7 @@ static int event_watcher(void *data, SDL_Event *event) {
}
#endif
static SDL_bool is_apk(const char *file) {
const char *ext = strrchr(file, '.');
return ext && !strcmp(ext, ".apk");
}
static SDL_bool event_loop(void) {
static void event_loop(void) {
#ifdef CONTINUOUS_RESIZING_WORKAROUND
SDL_AddEventWatch(event_watcher, NULL);
#endif
@@ -70,10 +69,10 @@ static SDL_bool event_loop(void) {
switch (event.type) {
case EVENT_DECODER_STOPPED:
LOGD("Video decoder stopped");
return SDL_FALSE;
return;
case SDL_QUIT:
LOGD("User requested to quit");
return SDL_TRUE;
return;
case EVENT_NEW_FRAME:
if (!screen.has_frame) {
screen.has_frame = SDL_TRUE;
@@ -81,7 +80,7 @@ static SDL_bool event_loop(void) {
screen_show_window(&screen);
}
if (!screen_update_frame(&screen, &frames)) {
return SDL_FALSE;
return;
}
break;
case SDL_WINDOWEVENT:
@@ -109,19 +108,8 @@ static SDL_bool event_loop(void) {
case SDL_MOUSEBUTTONUP:
input_manager_process_mouse_button(&input_manager, &event.button);
break;
case SDL_DROPFILE: {
file_handler_action_t action;
if (is_apk(event.drop.file)) {
action = ACTION_INSTALL_APK;
} else {
action = ACTION_PUSH_FILE;
}
file_handler_request(&file_handler, action, event.drop.file);
break;
}
}
}
return SDL_FALSE;
}
static process_t set_show_touches_enabled(const char *serial, SDL_bool enabled) {
@@ -138,12 +126,11 @@ static void wait_show_touches(process_t process) {
}
SDL_bool scrcpy(const struct scrcpy_options *options) {
if (!server_start(&server, options->serial, options->port,
options->max_size, options->bit_rate, options->crop)) {
return SDL_FALSE;
if (!SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1")) {
LOGW("Cannot request to keep default signal handlers");
}
process_t proc_show_touches = PROCESS_NONE;
process_t proc_show_touches;
SDL_bool show_touches_waited;
if (options->show_touches) {
LOGI("Enable show_touches");
@@ -151,13 +138,23 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
show_touches_waited = SDL_FALSE;
}
#ifdef AUDIO_SUPPORT
if (options->forward_audio) {
if (!audio_forwarding_start(&audio_player, options->serial)) {
return SDL_FALSE;
}
}
#endif
SDL_bool ret = SDL_TRUE;
if (!SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1")) {
LOGW("Cannot request to keep default signal handlers");
if (!server_start(&server, options->serial, options->port,
options->max_size, options->bit_rate)) {
ret = SDL_FALSE;
goto finally_disable_audio_forwarding;
}
if (!sdl_init_and_configure()) {
if (!sdl_video_init()) {
ret = SDL_FALSE;
goto finally_destroy_server;
}
@@ -187,12 +184,6 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
goto finally_destroy_server;
}
if (!file_handler_init(&file_handler, server.serial)) {
ret = SDL_FALSE;
server_stop(&server);
goto finally_destroy_frames;
}
decoder_init(&decoder, &frames, device_socket);
// now we consumed the header values, the socket receives the video stream
@@ -200,7 +191,7 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
if (!decoder_start(&decoder)) {
ret = SDL_FALSE;
server_stop(&server);
goto finally_destroy_file_handler;
goto finally_destroy_frames;
}
if (!controller_init(&controller, device_socket)) {
@@ -223,11 +214,7 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
show_touches_waited = SDL_TRUE;
}
if (options->fullscreen) {
screen_switch_fullscreen(&screen);
}
ret = event_loop();
event_loop();
LOGD("quit...");
screen_destroy(&screen);
@@ -242,10 +229,6 @@ finally_stop_decoder:
// stop the server before decoder_join() to wake up the decoder
server_stop(&server);
decoder_join(&decoder);
finally_destroy_file_handler:
file_handler_stop(&file_handler);
file_handler_join(&file_handler);
file_handler_destroy(&file_handler);
finally_destroy_frames:
frames_destroy(&frames);
finally_destroy_server:
@@ -260,6 +243,12 @@ finally_destroy_server:
}
server_destroy(&server);
finally_disable_audio_forwarding:
#ifdef AUDIO_SUPPORT
if (options->forward_audio) {
audio_forwarding_stop(&audio_player);
}
#endif
return ret;
}

View File

@@ -2,15 +2,17 @@
#define SCRCPY_H
#include <SDL2/SDL_stdinc.h>
#include "config.h"
struct scrcpy_options {
const char *serial;
const char *crop;
Uint16 port;
Uint16 max_size;
Uint32 bit_rate;
SDL_bool show_touches;
SDL_bool fullscreen;
#ifdef AUDIO_SUPPORT
SDL_bool forward_audio;
#endif
};
SDL_bool scrcpy(const struct scrcpy_options *options);

View File

@@ -4,20 +4,18 @@
#include <string.h>
#include "icon.xpm"
#include "lock_util.h"
#include "lockutil.h"
#include "log.h"
#include "tiny_xpm.h"
#include "tinyxpm.h"
#define DISPLAY_MARGINS 96
SDL_bool sdl_init_and_configure(void) {
if (SDL_Init(SDL_INIT_VIDEO)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
SDL_bool sdl_video_init(void) {
if (SDL_InitSubSystem(SDL_INIT_VIDEO)) {
LOGC("Could not initialize SDL video: %s", SDL_GetError());
return SDL_FALSE;
}
atexit(SDL_Quit);
// Use the best available scale quality
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2")) {
LOGW("Could not enable bilinear filtering");

View File

@@ -35,7 +35,7 @@ struct screen {
}
// init SDL and set appropriate hints
SDL_bool sdl_init_and_configure(void);
SDL_bool sdl_video_init(void);
// initialize default values
void screen_init(struct screen *screen);

View File

@@ -3,7 +3,6 @@
#include <errno.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_timer.h>
@@ -77,8 +76,7 @@ static SDL_bool disable_tunnel(struct server *server) {
}
static process_t execute_server(const char *serial,
Uint16 max_size, Uint32 bit_rate,
const char *crop, SDL_bool tunnel_forward) {
Uint16 max_size, Uint32 bit_rate, SDL_bool tunnel_forward) {
char max_size_string[6];
char bit_rate_string[11];
sprintf(max_size_string, "%"PRIu16, max_size);
@@ -92,7 +90,6 @@ static process_t execute_server(const char *serial,
max_size_string,
bit_rate_string,
tunnel_forward ? "true" : "false",
crop ? crop : "",
};
return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
}
@@ -149,25 +146,20 @@ void server_init(struct server *server) {
}
SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port,
Uint16 max_size, Uint32 bit_rate, const char *crop) {
Uint16 max_size, Uint32 bit_rate) {
server->local_port = local_port;
if (serial) {
server->serial = SDL_strdup(serial);
if (!server->serial) {
return SDL_FALSE;
}
}
if (!push_server(serial)) {
SDL_free((void *) server->serial);
return SDL_FALSE;
}
server->server_copied_to_device = SDL_TRUE;
if (!enable_tunnel(server)) {
SDL_free((void *) server->serial);
return SDL_FALSE;
}
@@ -184,20 +176,17 @@ SDL_bool server_start(struct server *server, const char *serial, Uint16 local_po
if (server->server_socket == INVALID_SOCKET) {
LOGE("Could not listen on port %" PRIu16, local_port);
disable_tunnel(server);
SDL_free((void *) server->serial);
return SDL_FALSE;
}
}
// server will connect to our server socket
server->process = execute_server(serial, max_size, bit_rate, crop,
server->tunnel_forward);
server->process = execute_server(serial, max_size, bit_rate, server->tunnel_forward);
if (server->process == PROCESS_NONE) {
if (!server->tunnel_forward) {
close_socket(&server->server_socket);
}
disable_tunnel(server);
SDL_free((void *) server->serial);
return SDL_FALSE;
}
@@ -210,7 +199,7 @@ socket_t server_connect_to(struct server *server) {
if (!server->tunnel_forward) {
server->device_socket = net_accept(server->server_socket);
} else {
Uint32 attempts = 100;
Uint32 attempts = 50;
Uint32 delay = 100; // ms
server->device_socket = connect_to_server(server->local_port, attempts, delay);
}

View File

@@ -31,7 +31,7 @@ void server_init(struct server *server);
// push, enable tunnel et start the server
SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port,
Uint16 max_size, Uint32 bit_rate, const char *crop);
Uint16 max_size, Uint32 bit_rate);
// block until the communication with the server is established
socket_t server_connect_to(struct server *server);

View File

@@ -1,7 +1,4 @@
#include "str_util.h"
#include <stdlib.h>
#include <string.h>
#include "strutil.h"
size_t xstrncpy(char *dest, const char *src, size_t n) {
size_t i;
@@ -34,16 +31,3 @@ truncated:
dst[n - 1] = '\0';
return n;
}
char *strquote(const char *src) {
size_t len = strlen(src);
char *quoted = malloc(len + 3);
if (!quoted) {
return NULL;
}
memcpy(&quoted[1], src, len);
quoted[0] = '"';
quoted[len + 1] = '"';
quoted[len + 2] = '\0';
return quoted;
}

View File

@@ -16,8 +16,4 @@ size_t xstrncpy(char *dest, const char *src, size_t n);
// occurred, or n if truncated
size_t xstrjoin(char *dst, const char *const tokens[], char sep, size_t n);
// quote a string
// returns the new allocated string, to be freed by the caller
char *strquote(const char *src);
#endif

View File

@@ -1,80 +1,112 @@
#include "command.h"
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "log.h"
enum process_result cmd_execute(const char *path, const char *const argv[], pid_t *pid) {
int fd[2];
if (pipe(fd) == -1) {
perror("pipe");
return PROCESS_ERROR_GENERIC;
}
enum process_result ret = PROCESS_SUCCESS;
*pid = fork();
if (*pid == -1) {
pid_t cmd_execute(const char *path, const char *const argv[]) {
pid_t pid = fork();
if (pid == -1) {
perror("fork");
ret = PROCESS_ERROR_GENERIC;
goto end;
return -1;
}
if (pid == 0) {
execvp(path, (char *const *)argv);
perror("exec");
_exit(1);
}
return pid;
}
pid_t cmd_execute_redirect(const char *path, const char *const argv[],
int *pipe_stdin, int *pipe_stdout, int *pipe_stderr) {
int in[2];
int out[2];
int err[2];
if (pipe_stdin) {
if (pipe(in) == -1) {
perror("pipe");
return -1;
}
}
if (pipe_stdout) {
if (pipe(out) == -1) {
perror("pipe");
// clean up
if (pipe_stdin) {
close(in[0]);
close(in[1]);
}
return -1;
}
}
if (pipe_stderr) {
if (pipe(err) == -1) {
perror("pipe");
// clean up
if (pipe_stdout) {
close(out[0]);
close(out[1]);
}
if (pipe_stdin) {
close(in[0]);
close(in[1]);
}
return -1;
}
}
if (*pid > 0) {
// parent close write side
close(fd[1]);
fd[1] = -1;
// wait for EOF or receive errno from child
if (read(fd[0], &ret, sizeof(ret)) == -1) {
perror("read");
ret = PROCESS_ERROR_GENERIC;
goto end;
}
} else if (*pid == 0) {
// child close read side
close(fd[0]);
if (fcntl(fd[1], F_SETFD, FD_CLOEXEC) == 0) {
execvp(path, (char *const *)argv);
if (errno == ENOENT) {
ret = PROCESS_ERROR_MISSING_BINARY;
} else {
ret = PROCESS_ERROR_GENERIC;
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return -1;
}
if (pid == 0) {
if (pipe_stdin) {
if (in[0] != STDIN_FILENO) {
dup2(in[0], STDIN_FILENO);
close(in[0]);
}
perror("exec");
} else {
perror("fcntl");
ret = PROCESS_ERROR_GENERIC;
close(in[1]);
}
// send ret to the parent
if (write(fd[1], &ret, sizeof(ret)) == -1) {
perror("write");
if (pipe_stdout) {
if (out[1] != STDOUT_FILENO) {
dup2(out[1], STDOUT_FILENO);
close(out[1]);
}
close(out[0]);
}
// close write side before exiting
close(fd[1]);
if (pipe_stderr) {
if (err[1] != STDERR_FILENO) {
dup2(err[1], STDERR_FILENO);
close(err[1]);
}
close(err[0]);
}
execvp(path, (char *const *)argv);
perror("exec");
_exit(1);
}
end:
if (fd[0] != -1) {
close(fd[0]);
if (pipe_stdin) {
close(in[0]);
*pipe_stdin = in[1];
}
if (fd[1] != -1) {
close(fd[1]);
if (pipe_stdout) {
*pipe_stdout = out[0];
close(out[1]);
}
return ret;
if (pipe_stderr) {
*pipe_stderr = err[0];
close(err[1]);
}
return pid;
}
SDL_bool cmd_terminate(pid_t pid) {
if (pid <= 0) {
LOGC("Requested to kill %d, this is an error. Please report the bug.\n", (int) pid);
abort();
}
return kill(pid, SIGTERM) != -1;
}
@@ -92,3 +124,13 @@ SDL_bool cmd_simple_wait(pid_t pid, int *exit_code) {
}
return !code;
}
int read_pipe(int pipe, char *data, size_t len) {
return read(pipe, data, len);
}
void close_pipe(int pipe) {
if (close(pipe)) {
perror("close pipe");
}
}

View File

@@ -1,8 +1,7 @@
#include "command.h"
#include "config.h"
#include "log.h"
#include "str_util.h"
#include "strutil.h"
static int build_cmd(char *cmd, size_t len, const char *const argv[]) {
// Windows command-line parsing is WTF:
@@ -17,7 +16,7 @@ static int build_cmd(char *cmd, size_t len, const char *const argv[]) {
return 0;
}
enum process_result cmd_execute(const char *path, const char *const argv[], HANDLE *handle) {
HANDLE cmd_execute(const char *path, const char *const argv[]) {
STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
@@ -25,25 +24,83 @@ enum process_result cmd_execute(const char *path, const char *const argv[], HAND
char cmd[256];
if (build_cmd(cmd, sizeof(cmd), argv)) {
*handle = NULL;
return PROCESS_ERROR_GENERIC;
return NULL;
}
#ifdef WINDOWS_NOCONSOLE
int flags = CREATE_NO_WINDOW;
#else
int flags = 0;
#endif
if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi)) {
*handle = NULL;
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
return PROCESS_ERROR_MISSING_BINARY;
if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
return NULL;
}
return pi.hProcess;
}
HANDLE cmd_execute_redirect(const char *path, const char *const argv[],
HANDLE *pipe_stdin, HANDLE *pipe_stdout, HANDLE *pipe_stderr) {
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
HANDLE stdin_read_handle;
HANDLE stdout_write_handle;
HANDLE stderr_write_handle;
if (pipe_stdin) {
if (!CreatePipe(&stdin_read_handle, pipe_stdin, &sa, 0)) {
perror("pipe");
return NULL;
}
}
if (pipe_stdout) {
if (!CreatePipe(pipe_stdout, &stdout_write_handle, &sa, 0)) {
perror("pipe");
// clean up
if (pipe_stdin) {
CloseHandle(&stdin_read_handle);
CloseHandle(pipe_stdin);
}
return NULL;
}
}
if (pipe_stderr) {
if (!CreatePipe(pipe_stderr, &stderr_write_handle, &sa, 0)) {
perror("pipe");
// clean up
if (pipe_stdin) {
CloseHandle(&stdin_read_handle);
CloseHandle(pipe_stdin);
}
if (pipe_stdout) {
CloseHandle(pipe_stdout);
CloseHandle(&stdout_write_handle);
}
}
return PROCESS_ERROR_GENERIC;
}
*handle = pi.hProcess;
return PROCESS_SUCCESS;
STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
if (pipe_stdin) {
si.hStdInput = stdin_read_handle;
}
if (pipe_stdout) {
si.hStdOutput = stdout_write_handle;
}
if (pipe_stderr) {
si.hStdError = stderr_write_handle;
}
char cmd[256];
if (build_cmd(cmd, sizeof(cmd), argv)) {
return NULL;
}
if (!CreateProcess(NULL, cmd, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
return NULL;
}
return pi.hProcess;
}
SDL_bool cmd_terminate(HANDLE handle) {
@@ -61,3 +118,17 @@ SDL_bool cmd_simple_wait(HANDLE handle, DWORD *exit_code) {
}
return !code;
}
int read_pipe(HANDLE pipe, char *data, size_t len) {
DWORD r;
if (!ReadFile(pipe, data, len, &r, NULL)) {
return -1;
}
return r;
}
void close_pipe(HANDLE pipe) {
if (!CloseHandle(pipe)) {
LOGW("Cannot close pipe");
}
}

View File

@@ -1,4 +1,4 @@
#include "tiny_xpm.h"
#include "tinyxpm.h"
#include <stdio.h>
#include <stdlib.h>
@@ -40,15 +40,15 @@ SDL_Surface *read_xpm(char *xpm[]) {
// *** No error handling, assume the XPM source is valid ***
// (it's in our source repo)
// Assertions are only checked in debug
int width = strtol(xpm[0], &endptr, 10);
int height = strtol(endptr + 1, &endptr, 10);
int colors = strtol(endptr + 1, &endptr, 10);
int chars = strtol(endptr + 1, &endptr, 10);
Uint32 width = strtol(xpm[0], &endptr, 10);
Uint32 height = strtol(endptr + 1, &endptr, 10);
Uint32 colors = strtol(endptr + 1, &endptr, 10);
Uint32 chars = strtol(endptr + 1, &endptr, 10);
// sanity checks
SDL_assert(0 <= width && width < 256);
SDL_assert(0 <= height && height < 256);
SDL_assert(0 <= colors && colors < 256);
SDL_assert(width < 256);
SDL_assert(height < 256);
SDL_assert(colors < 256);
SDL_assert(chars == 1); // this implementation does not support more
// init index

View File

@@ -1,9 +1,8 @@
#include <assert.h>
#include <string.h>
#include "control_event.h"
#include "controlevent.h"
static void test_control_event_queue_empty(void) {
static void test_control_event_queue_empty() {
struct control_event_queue queue;
SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
@@ -25,7 +24,7 @@ static void test_control_event_queue_empty(void) {
control_event_queue_destroy(&queue);
}
static void test_control_event_queue_full(void) {
static void test_control_event_queue_full() {
struct control_event_queue queue;
SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
@@ -43,7 +42,7 @@ static void test_control_event_queue_full(void) {
control_event_queue_destroy(&queue);
}
static void test_control_event_queue_push_take(void) {
static void test_control_event_queue_push_take() {
struct control_event_queue queue;
SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
@@ -87,7 +86,7 @@ static void test_control_event_queue_push_take(void) {
control_event_queue_destroy(&queue);
}
int main(void) {
int main() {
test_control_event_queue_empty();
test_control_event_queue_full();
test_control_event_queue_push_take();

View File

@@ -1,9 +1,8 @@
#include <assert.h>
#include <string.h>
#include "control_event.h"
#include "controlevent.h"
static void test_serialize_keycode_event(void) {
static void test_serialize_keycode_event() {
struct control_event event = {
.type = CONTROL_EVENT_TYPE_KEYCODE,
.keycode_event = {
@@ -26,7 +25,7 @@ static void test_serialize_keycode_event(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_text_event(void) {
static void test_serialize_text_event() {
struct control_event event = {
.type = CONTROL_EVENT_TYPE_TEXT,
.text_event = {
@@ -46,7 +45,7 @@ static void test_serialize_text_event(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_long_text_event(void) {
static void test_serialize_long_text_event() {
struct control_event event;
event.type = CONTROL_EVENT_TYPE_TEXT;
char text[TEXT_MAX_LENGTH];
@@ -66,7 +65,7 @@ static void test_serialize_long_text_event(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_mouse_event(void) {
static void test_serialize_mouse_event() {
struct control_event event = {
.type = CONTROL_EVENT_TYPE_MOUSE,
.mouse_event = {
@@ -99,7 +98,7 @@ static void test_serialize_mouse_event(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_scroll_event(void) {
static void test_serialize_scroll_event() {
struct control_event event = {
.type = CONTROL_EVENT_TYPE_SCROLL,
.scroll_event = {
@@ -132,10 +131,11 @@ static void test_serialize_scroll_event(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
int main(void) {
int main() {
test_serialize_keycode_event();
test_serialize_text_event();
test_serialize_long_text_event();
test_serialize_mouse_event();
test_serialize_scroll_event();
return 0;
}

View File

@@ -1,9 +1,9 @@
#include <assert.h>
#include <string.h>
#include "str_util.h"
#include "strutil.h"
static void test_xstrncpy_simple(void) {
static void test_xstrncpy_simple() {
char s[] = "xxxxxxxxxx";
size_t w = xstrncpy(s, "abcdef", sizeof(s));
@@ -20,7 +20,7 @@ static void test_xstrncpy_simple(void) {
assert(!strcmp("abcdef", s));
}
static void test_xstrncpy_just_fit(void) {
static void test_xstrncpy_just_fit() {
char s[] = "xxxxxx";
size_t w = xstrncpy(s, "abcdef", sizeof(s));
@@ -34,7 +34,7 @@ static void test_xstrncpy_just_fit(void) {
assert(!strcmp("abcdef", s));
}
static void test_xstrncpy_truncated(void) {
static void test_xstrncpy_truncated() {
char s[] = "xxx";
size_t w = xstrncpy(s, "abcdef", sizeof(s));
@@ -48,7 +48,7 @@ static void test_xstrncpy_truncated(void) {
assert(!strncmp("abcdef", s, 3));
}
static void test_xstrjoin_simple(void) {
static void test_xstrjoin_simple() {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxxxxxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
@@ -66,7 +66,7 @@ static void test_xstrjoin_simple(void) {
assert(!strcmp("abc de fghi", s));
}
static void test_xstrjoin_just_fit(void) {
static void test_xstrjoin_just_fit() {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
@@ -81,7 +81,7 @@ static void test_xstrjoin_just_fit(void) {
assert(!strcmp("abc de fghi", s));
}
static void test_xstrjoin_truncated_in_token(void) {
static void test_xstrjoin_truncated_in_token() {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
@@ -96,7 +96,7 @@ static void test_xstrjoin_truncated_in_token(void) {
assert(!strcmp("abc d", s));
}
static void test_xstrjoin_truncated_before_sep(void) {
static void test_xstrjoin_truncated_before_sep() {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
@@ -111,7 +111,7 @@ static void test_xstrjoin_truncated_before_sep(void) {
assert(!strcmp("abc de", s));
}
static void test_xstrjoin_truncated_after_sep(void) {
static void test_xstrjoin_truncated_after_sep() {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
@@ -126,7 +126,7 @@ static void test_xstrjoin_truncated_after_sep(void) {
assert(!strcmp("abc de ", s));
}
int main(void) {
int main() {
test_xstrncpy_simple();
test_xstrncpy_just_fit();
test_xstrncpy_truncated();

View File

@@ -7,7 +7,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.1'
classpath 'com.android.tools.build:gradle:3.0.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@@ -1,20 +0,0 @@
# apt install mingw-w64 mingw-w64-tools
[binaries]
name = 'mingw'
c = '/usr/bin/i686-w64-mingw32-gcc'
cpp = '/usr/bin/i686-w64-mingw32-g++'
ar = '/usr/bin/i686-w64-mingw32-ar'
strip = '/usr/bin/i686-w64-mingw32-strip'
pkgconfig = '/usr/bin/i686-w64-mingw32-pkg-config'
[host_machine]
system = 'windows'
cpu_family = 'x86'
cpu = 'i686'
endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.0.2-win32-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.0.2-win32-dev'
prebuilt_sdl2 = 'SDL2-2.0.8/i686-w64-mingw32'

View File

@@ -1,20 +0,0 @@
# apt install mingw-w64 mingw-w64-tools
[binaries]
name = 'mingw'
c = '/usr/bin/x86_64-w64-mingw32-gcc'
cpp = '/usr/bin/x86_64-w64-mingw32-g++'
ar = '/usr/bin/x86_64-w64-mingw32-ar'
strip = '/usr/bin/x86_64-w64-mingw32-strip'
pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config'
[host_machine]
system = 'windows'
cpu_family = 'x86'
cpu = 'x86_64'
endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.0.2-win64-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.0.2-win64-dev'
prebuilt_sdl2 = 'SDL2-2.0.8/x86_64-w64-mingw32'

View File

@@ -1,6 +1,6 @@
#Mon Jun 04 11:48:32 CEST 2018
#Mon Jan 29 16:38:49 CET 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip

View File

@@ -1,4 +1,4 @@
project('scrcpy', 'c', meson_version: '>= 0.37', default_options : 'c_std=c11')
project('scrcpy', 'c', meson_version: '>= 0.37')
if get_option('build_app')
subdir('app')

View File

@@ -1,8 +1,7 @@
option('build_app', type: 'boolean', value: true, description: 'Build the client')
option('build_server', type: 'boolean', value: true, description: 'Build the server')
option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux')
option('windows_noconsole', type: 'boolean', value: false, description: 'Disable console on Windows (pass -mwindows flag)')
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
option('override_server_path', type: 'string', description: 'Hardcoded path to find the server at runtime')
option('skip_frames', type: 'boolean', value: true, description: 'Always display the most recent frame')
option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support')
option('audio_support', type: 'boolean', value: true, description: 'Enable audio support')

View File

@@ -1,4 +0,0 @@
*
!/.gitignore
!/Makefile
!/prepare-dep

View File

@@ -1,40 +0,0 @@
.PHONY: prepare-win32 prepare-win64 \
prepare-ffmpeg-shared-win32 \
prepare-ffmpeg-dev-win32 \
prepare-ffmpeg-shared-win64 \
prepare-ffmpeg-dev-win64 \
prepare-sdl2 \
prepare-adb
prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32 prepare-adb
prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb
prepare-ffmpeg-shared-win32:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.0.2-win32-shared.zip \
cc190a3a4cf7bfbd4fbaa92609c1501a1de458055e6cfea8b745c1d515013aa8 \
ffmpeg-4.0.2-win32-shared
prepare-ffmpeg-dev-win32:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.0.2-win32-dev.zip \
c72c74bad74ac0541f1b43090c26a50017c49041c182a703abd2057bb8cdc238 \
ffmpeg-4.0.2-win32-dev
prepare-ffmpeg-shared-win64:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.0.2-win64-shared.zip \
ede566aca8b5348dff85570f9638c6bad33209f9419f79db7bde7daa37599bff \
ffmpeg-4.0.2-win64-shared
prepare-ffmpeg-dev-win64:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.0.2-win64-dev.zip \
23ee994161c51285cb956b98d3caa499d48083dae7b26c1fdf77f22e98df1c5f \
ffmpeg-4.0.2-win64-dev
prepare-sdl2:
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.8-mingw.tar.gz \
ffff7305d634aff5e1df5b7bb935435c3a02c8b03ad94a1a2be9169a558a7961 \
SDL2-2.0.8
prepare-adb:
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r28.0.1-windows.zip \
db78f726d5dc653706dcd15a462ab1b946c643f598df76906c4c1858411c54df \
platform-tools

View File

@@ -1,58 +0,0 @@
#!/bin/bash
set -e
url="$1"
sum="$2"
dir="$3"
checksum() {
local file="$1"
local sum="$2"
echo "$file: verifying checksum..."
echo "$sum $file" | sha256sum -c
}
get_file() {
local url="$1"
local file="$2"
local sum="$3"
if [[ -f "$file" ]]
then
echo "$file: found"
else
echo "$file: not found, downloading..."
wget "$url" -O "$file"
fi
checksum "$file" "$sum"
}
extract() {
local file="$1"
echo "Extracting $file..."
if [[ "$file" == *.zip ]]
then
unzip -q "$file"
elif [[ "$file" == *.tar.gz ]]
then
tar xf "$file"
else
echo "Unsupported file: $file"
return 1
fi
}
get_dep() {
local url="$1"
local sum="$2"
local dir="$3"
local file="${url##*/}"
if [[ -d "$dir" ]]
then
echo "$dir: found"
else
echo "$dir: not found"
get_file "$url" "$file" "$sum"
extract "$file"
fi
}
get_dep "$1" "$2" "$3"

View File

@@ -6,8 +6,8 @@ android {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 27
versionCode 5
versionName "1.4"
versionCode 2
versionName "1.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@@ -5,9 +5,9 @@ import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public final class DesktopConnection implements Closeable {
@@ -18,14 +18,14 @@ public final class DesktopConnection implements Closeable {
private final LocalSocket socket;
private final InputStream inputStream;
private final FileDescriptor fd;
private final OutputStream outputStream;
private final ControlEventReader reader = new ControlEventReader();
private DesktopConnection(LocalSocket socket) throws IOException {
this.socket = socket;
inputStream = socket.getInputStream();
fd = socket.getFileDescriptor();
outputStream = socket.getOutputStream();
}
private static LocalSocket connect(String abstractName) throws IOException {
@@ -78,11 +78,11 @@ public final class DesktopConnection implements Closeable {
buffer[DEVICE_NAME_FIELD_LENGTH + 1] = (byte) width;
buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8);
buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height;
IO.writeFully(fd, buffer, 0, buffer.length);
outputStream.write(buffer, 0, buffer.length);
}
public FileDescriptor getFd() {
return fd;
public OutputStream getOutputStream() {
return outputStream;
}
public ControlEvent receiveControlEvent() throws IOException {

View File

@@ -3,7 +3,6 @@ package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.os.RemoteException;
import android.view.IRotationWatcher;
@@ -21,7 +20,7 @@ public final class Device {
private RotationListener rotationListener;
public Device(Options options) {
screenInfo = computeScreenInfo(options.getCrop(), options.getMaxSize());
screenInfo = computeScreenInfo(options.getMaxSize());
registerRotationWatcher(new IRotationWatcher.Stub() {
@Override
public void onRotationChanged(int rotation) throws RemoteException {
@@ -41,40 +40,18 @@ public final class Device {
return screenInfo;
}
private ScreenInfo computeScreenInfo(Rect crop, int maxSize) {
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo();
boolean rotated = (displayInfo.getRotation() & 1) != 0;
Size deviceSize = displayInfo.getSize();
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
if (crop != null) {
if (rotated) {
// the crop (provided by the user) is expressed in the natural orientation
crop = flipRect(crop);
}
if (!contentRect.intersect(crop)) {
// intersect() changes contentRect so that it is intersected with crop
Ln.w("Crop rectangle (" + formatCrop(crop) + ") does not intersect device screen (" + formatCrop(deviceSize.toRect()) + ")");
contentRect = new Rect(); // empty
}
}
Size videoSize = computeVideoSize(contentRect.width(), contentRect.height(), maxSize);
return new ScreenInfo(contentRect, videoSize, rotated);
}
private static String formatCrop(Rect rect) {
return rect.width() + ":" + rect.height() + ":" + rect.left + ":" + rect.top;
}
@SuppressWarnings("checkstyle:MagicNumber")
private static Size computeVideoSize(int w, int h, int maxSize) {
private ScreenInfo computeScreenInfo(int maxSize) {
// Compute the video size and the padding of the content inside this video.
// Principle:
// - scale down the great side of the screen to maxSize (if necessary);
// - scale down the other side so that the aspect ratio is preserved;
// - round this value to the nearest multiple of 8 (H.264 only accepts multiples of 8)
w &= ~7; // in case it's not a multiple of 8
h &= ~7;
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo();
boolean rotated = (displayInfo.getRotation() & 1) != 0;
Size deviceSize = displayInfo.getSize();
int w = deviceSize.getWidth() & ~7; // in case it's not a multiple of 8
int h = deviceSize.getHeight() & ~7;
if (maxSize > 0) {
if (BuildConfig.DEBUG && maxSize % 8 != 0) {
throw new AssertionError("Max size must be a multiple of 8");
@@ -91,12 +68,12 @@ public final class Device {
w = portrait ? minor : major;
h = portrait ? major : minor;
}
return new Size(w, h);
Size videoSize = new Size(w, h);
return new ScreenInfo(deviceSize, videoSize, rotated);
}
public Point getPhysicalPoint(Position position) {
// it hides the field on purpose, to read it with a lock
@SuppressWarnings("checkstyle:HiddenField")
@SuppressWarnings("checkstyle:HiddenField") // it hides the field on purpose, to read it with a lock
ScreenInfo screenInfo = getScreenInfo(); // read with synchronization
Size videoSize = screenInfo.getVideoSize();
Size clientVideoSize = position.getScreenSize();
@@ -105,10 +82,10 @@ public final class Device {
// the device may have been rotated since the event was generated, so ignore the event
return null;
}
Rect contentRect = screenInfo.getContentRect();
Size deviceSize = screenInfo.getDeviceSize();
Point point = position.getPoint();
int scaledX = contentRect.left + point.x * contentRect.width() / videoSize.getWidth();
int scaledY = contentRect.top + point.y * contentRect.height() / videoSize.getHeight();
int scaledX = point.x * deviceSize.getWidth() / videoSize.getWidth();
int scaledY = point.y * deviceSize.getHeight() / videoSize.getHeight();
return new Point(scaledX, scaledY);
}
@@ -131,8 +108,4 @@ public final class Device {
public synchronized void setRotationListener(RotationListener rotationListener) {
this.rotationListener = rotationListener;
}
static Rect flipRect(Rect crop) {
return new Rect(crop.top, crop.left, crop.bottom, crop.right);
}
}

View File

@@ -1,40 +0,0 @@
package com.genymobile.scrcpy;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import java.io.FileDescriptor;
import java.io.IOException;
import java.nio.ByteBuffer;
public class IO {
private IO() {
// not instantiable
}
public static void writeFully(FileDescriptor fd, ByteBuffer from) throws IOException {
// ByteBuffer position is not updated as expected by Os.write() on old Android versions, so
// count the remaining bytes manually.
// See <https://github.com/Genymobile/scrcpy/issues/291>.
int remaining = from.remaining();
while (remaining > 0) {
try {
int w = Os.write(fd, from);
if (BuildConfig.DEBUG && w < 0) {
// w should not be negative, since an exception is thrown on error
throw new AssertionError("Os.write() returned a negative value (" + w + ")");
}
remaining -= w;
} catch (ErrnoException e) {
if (e.errno != OsConstants.EINTR) {
throw new IOException(e);
}
}
}
}
public static void writeFully(FileDescriptor fd, byte[] buffer, int offset, int len) throws IOException {
writeFully(fd, ByteBuffer.wrap(buffer, offset, len));
}
}

View File

@@ -1,12 +1,9 @@
package com.genymobile.scrcpy;
import android.graphics.Rect;
public class Options {
private int maxSize;
private int bitRate;
private boolean tunnelForward;
private Rect crop;
public int getMaxSize() {
return maxSize;
@@ -31,12 +28,4 @@ public class Options {
public void setTunnelForward(boolean tunnelForward) {
this.tunnelForward = tunnelForward;
}
public Rect getCrop() {
return crop;
}
public void setCrop(Rect crop) {
this.crop = crop;
}
}

View File

@@ -9,8 +9,8 @@ import android.media.MediaFormat;
import android.os.IBinder;
import android.view.Surface;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -48,7 +48,7 @@ public class ScreenEncoder implements Device.RotationListener {
return rotationChanged.getAndSet(false);
}
public void streamScreen(Device device, FileDescriptor fd) throws IOException {
public void streamScreen(Device device, OutputStream outputStream) throws IOException {
MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval);
device.setRotationListener(this);
boolean alive;
@@ -56,15 +56,15 @@ public class ScreenEncoder implements Device.RotationListener {
do {
MediaCodec codec = createCodec();
IBinder display = createDisplay();
Rect contentRect = device.getScreenInfo().getContentRect();
Rect deviceRect = device.getScreenInfo().getDeviceSize().toRect();
Rect videoRect = device.getScreenInfo().getVideoSize().toRect();
setSize(format, videoRect.width(), videoRect.height());
configure(codec, format);
Surface surface = codec.createInputSurface();
setDisplaySurface(display, surface, contentRect, videoRect);
setDisplaySurface(display, surface, deviceRect, videoRect);
codec.start();
try {
alive = encode(codec, fd);
alive = encode(codec, outputStream);
} finally {
codec.stop();
destroyDisplay(display);
@@ -77,7 +77,9 @@ public class ScreenEncoder implements Device.RotationListener {
}
}
private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException {
private boolean encode(MediaCodec codec, OutputStream outputStream) throws IOException {
@SuppressWarnings("checkstyle:MagicNumber")
byte[] buf = new byte[bitRate / 8]; // may contain up to 1 second of video
boolean eof = false;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while (!consumeRotationChange() && !eof) {
@@ -89,8 +91,15 @@ public class ScreenEncoder implements Device.RotationListener {
break;
}
if (outputBufferId >= 0) {
ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId);
IO.writeFully(fd, codecBuffer);
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
while (outputBuffer.hasRemaining()) {
int remaining = outputBuffer.remaining();
int len = Math.min(buf.length, remaining);
// the outputBuffer is probably direct (it has no underlying array), and LocalSocket does not expose channels,
// so we must copy the data locally to write them manually to the output stream
outputBuffer.get(buf, 0, len);
outputStream.write(buf, 0, len);
}
}
} finally {
if (outputBufferId >= 0) {

View File

@@ -1,20 +1,18 @@
package com.genymobile.scrcpy;
import android.graphics.Rect;
public final class ScreenInfo {
private final Rect contentRect; // device size, possibly cropped
private final Size deviceSize;
private final Size videoSize;
private final boolean rotated;
public ScreenInfo(Rect contentRect, Size videoSize, boolean rotated) {
this.contentRect = contentRect;
public ScreenInfo(Size deviceSize, Size videoSize, boolean rotated) {
this.deviceSize = deviceSize;
this.videoSize = videoSize;
this.rotated = rotated;
}
public Rect getContentRect() {
return contentRect;
public Size getDeviceSize() {
return deviceSize;
}
public Size getVideoSize() {
@@ -26,6 +24,6 @@ public final class ScreenInfo {
if (rotated == newRotated) {
return this;
}
return new ScreenInfo(Device.flipRect(contentRect), videoSize.rotate(), newRotated);
return new ScreenInfo(deviceSize.rotate(), videoSize.rotate(), newRotated);
}
}

View File

@@ -1,7 +1,5 @@
package com.genymobile.scrcpy;
import android.graphics.Rect;
import java.io.IOException;
public final class Server {
@@ -21,7 +19,7 @@ public final class Server {
try {
// synchronous
screenEncoder.streamScreen(device, connection.getFd());
screenEncoder.streamScreen(device, connection.getOutputStream());
} catch (IOException e) {
// this is expected on close
Ln.d("Screen streaming stopped");
@@ -65,31 +63,9 @@ public final class Server {
boolean tunnelForward = Boolean.parseBoolean(args[2]);
options.setTunnelForward(tunnelForward);
if (args.length < 4) {
return options;
}
Rect crop = parseCrop(args[3]);
options.setCrop(crop);
return options;
}
private static Rect parseCrop(String crop) {
if (crop.isEmpty()) {
return null;
}
// input format: "width:height:x:y"
String[] tokens = crop.split(":");
if (tokens.length != 4) {
throw new IllegalArgumentException("Crop must contains 4 values separated by colons: \"" + crop + "\"");
}
int width = Integer.parseInt(tokens[0]);
int height = Integer.parseInt(tokens[1]);
int x = Integer.parseInt(tokens[2]);
int y = Integer.parseInt(tokens[3]);
return new Rect(x, y, x + width, y + height);
}
public static void main(String... args) throws Exception {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override