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
43 changed files with 1015 additions and 917 deletions

23
FAQ.md
View File

@@ -7,8 +7,29 @@ with it.
Here are the common reported problems and their status. 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. The most common is your device not being detected by `adb`, or is unauthorized.
Check everything is ok by calling: Check everything is ok by calling:

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-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0-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-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.0-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

106
README.md
View File

@@ -1,8 +1,8 @@
# scrcpy (v1.2) # scrcpy
This application provides display and control of Android devices connected on 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. USB. It does not require any _root_ access. It works on _GNU/Linux_, _Windows_
It works on _GNU/Linux_, _Windows_ and _MacOS_. and _MacOS_.
![screenshot](assets/screenshot-debian-600.jpg) ![screenshot](assets/screenshot-debian-600.jpg)
@@ -14,19 +14,24 @@ The Android part requires at least API 21 (Android 5.0).
You need [adb]. It is available in the [Android SDK platform You need [adb]. It is available in the [Android SDK platform
tools][platform-tools], or packaged in your distribution (`android-adb-tools`). tools][platform-tools], or packaged in your distribution (`android-adb-tools`).
On Windows, just [download scrcpy for Windows](#windows), `adb` is included. 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). Make sure you [enabled adb debugging][enable-adb] on your device(s).
The client requires [FFmpeg] and [LibSDL2]. On Windows, they are included in the The client requires [FFmpeg], [LibSDL2] and [LibUSB].
[prebuilt application](#windows).
[adb]: https://developer.android.com/studio/command-line/adb.html [adb]: https://developer.android.com/studio/command-line/adb.html
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling [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]: https://developer.android.com/studio/releases/platform-tools.html
[platform-tools-windows]: https://dl.google.com/android/repository/platform-tools-latest-windows.zip [platform-tools-windows]: https://dl.google.com/android/repository/platform-tools-latest-windows.zip
[ffmpeg]: https://en.wikipedia.org/wiki/FFmpeg [ffmpeg]: https://en.wikipedia.org/wiki/FFmpeg
[LibSDL2]: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer [libsdl2]: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
[libusb]: https://en.wikipedia.org/wiki/Libusb
## Build and install ## Build and install
@@ -40,12 +45,12 @@ Install the required packages from your package manager.
```bash ```bash
# runtime dependencies # runtime dependencies
sudo apt install ffmpeg libsdl2-2.0.0 sudo apt install ffmpeg libsdl2-2.0.0 libusb-1.0-0
# client build dependencies # client build dependencies
sudo apt install make gcc pkg-config meson \ sudo apt install make gcc pkg-config meson \
libavcodec-dev libavformat-dev libavutil-dev \ libavcodec-dev libavformat-dev libavutil-dev \
libsdl2-dev libsdl2-dev libusb-1.0-0-dev
# server build dependencies # server build dependencies
sudo apt install openjdk-8-jdk sudo apt install openjdk-8-jdk
@@ -76,54 +81,18 @@ Two [AUR] packages have been created by users:
#### Windows #### Windows
For Windows, for simplicity, prebuilt archives with all the dependencies For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) are available: (including `adb`) is available:
- [`scrcpy-win32-v1.2.zip`][direct-win32]. - [`scrcpy-windows-with-deps-v1.1.zip`][direct-windows-with-deps].
_(SHA-256: a1fe1de67ec75dcf970ca5d97a04c26ff0f2d61871f2ef51b6f2f0bf666966b2)_ _(SHA-256: 27eb36c15937601d1062c1dc0b45faae0e06fefea2019aadeb4fa7f76a07bb4c)_
- [`scrcpy-win64-v1.2.zip`][direct-win64].
_(SHA-256: 35ae3bcee51771e7c51b8a8be87aef2295c9f267606a7cf83ebb0a4d583ef536)_
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.2/scrcpy-win32-v1.2.zip [direct-windows-with-deps]: https://github.com/Genymobile/scrcpy/releases/download/v1.1/scrcpy-windows-with-deps-v1.1.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.2/scrcpy-win64-v1.2.zip
Instead, you may want to build it manually. _(It's just a portable version including _dll_ copied from MSYS2.)_
In that case, download the [platform-tools][platform-tools-windows] and extract Instead, you may want to build it manually. You need [MSYS2] to build the
the following files to a directory accessible from your `PATH`: project. From an MSYS2 terminal, install the required packages:
- `adb.exe`
- `AdbWinApi.dll`
- `AdbWinUsbApi.dll`
##### 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/ [MSYS2]: http://www.msys2.org/
@@ -136,7 +105,8 @@ pacman -S mingw-w64-x86_64-SDL2 \
pacman -S mingw-w64-x86_64-make \ pacman -S mingw-w64-x86_64-make \
mingw-w64-x86_64-gcc \ mingw-w64-x86_64-gcc \
mingw-w64-x86_64-pkg-config \ mingw-w64-x86_64-pkg-config \
mingw-w64-x86_64-meson mingw-w64-x86_64-meson \
mingw-w64-x86_64-libusb
``` ```
For a 32 bits version, replace `x86_64` by `i686`: For a 32 bits version, replace `x86_64` by `i686`:
@@ -178,7 +148,7 @@ Instead, you may want to build it manually. Install the packages:
brew install sdl2 ffmpeg brew install sdl2 ffmpeg
# client build dependencies # client build dependencies
brew install pkg-config meson brew install pkg-config meson libusb
``` ```
Additionally, if you want to build the server, install Java 8 from Caskroom, and Additionally, if you want to build the server, install Java 8 from Caskroom, and
@@ -247,10 +217,10 @@ 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 depend on your system and architecture, you may want to use the prebuilt binary
instead: instead:
- [`scrcpy-server-v1.2.jar`][direct-scrcpy-server]. - [`scrcpy-server-v1.1.jar`][direct-scrcpy-server].
_(SHA-256: cb39654ed2fda3d30ddff292806950ccc5c394375ea12b974f790c7f38f61f60)_ _(SHA-256: 14826512bf38447ec94adf3b531676ce038d19e7e06757fb4e537882b17e77b3)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.2/scrcpy-server-v1.2.jar [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. In that case, the build does not require Java or the Android SDK.
@@ -294,12 +264,6 @@ screen is smaller, or cannot decode such a high definition):
scrcpy -m 1024 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_: If several devices are listed in `adb devices`, you must specify the _serial_:
```bash ```bash
@@ -312,6 +276,12 @@ To show physical touches while scrcpy is running:
scrcpy -t scrcpy -t
``` ```
To enable audio forwarding:
```bash
scrcpy -a
```
To run without installing: To run without installing:
```bash ```bash
@@ -338,7 +308,6 @@ To run without installing:
| turn screen on | _Right-click²_ | | turn screen on | _Right-click²_ |
| paste computer clipboard to device | `Ctrl`+`v` | | paste computer clipboard to device | `Ctrl`+`v` |
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` | | enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
| install APK from computer | drag & drop APK file |
_¹Double-click on black borders to remove them._ _¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._ _²Right-click turns the screen on if it was off, presses BACK otherwise._
@@ -384,8 +353,5 @@ Read the [developers page].
## Articles ## Articles
- [Introducing scrcpy][article-intro] - [Introducing scrcpy](https://blog.rom1v.com/2018/03/introducing-scrcpy/)
- [Scrcpy now works wirelessly][article-tcpip] - [Scrcpy now works wirelessly](https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/)
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/

View File

@@ -9,7 +9,6 @@ src = [
'src/fpscounter.c', 'src/fpscounter.c',
'src/frames.c', 'src/frames.c',
'src/inputmanager.c', 'src/inputmanager.c',
'src/installer.c',
'src/lockutil.c', 'src/lockutil.c',
'src/net.c', 'src/net.c',
'src/scrcpy.c', 'src/scrcpy.c',
@@ -19,9 +18,13 @@ src = [
'src/tinyxpm.c', 'src/tinyxpm.c',
] ]
if not get_option('crossbuild_windows') if get_option('audio_support')
src += [
'src/aoa.c',
'src/audio.c'
]
endif
# native build
dependencies = [ dependencies = [
dependency('libavformat'), dependency('libavformat'),
dependency('libavcodec'), dependency('libavcodec'),
@@ -29,43 +32,8 @@ if not get_option('crossbuild_windows')
dependency('sdl2'), dependency('sdl2'),
] ]
else if get_option('audio_support')
dependencies += dependency('libusb-1.0')
# 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')
]
endif endif
cc = meson.get_compiler('c') cc = meson.get_compiler('c')
@@ -85,7 +53,7 @@ conf = configuration_data()
conf.set('BUILD_DEBUG', get_option('buildtype') == 'debug') conf.set('BUILD_DEBUG', get_option('buildtype') == 'debug')
# the version, updated on release # the version, updated on release
conf.set_quoted('SCRCPY_VERSION', '1.3') conf.set_quoted('SCRCPY_VERSION', '1.1')
# the prefix used during configuration (meson --prefix=PREFIX) # the prefix used during configuration (meson --prefix=PREFIX)
conf.set_quoted('PREFIX', get_option('prefix')) conf.set_quoted('PREFIX', get_option('prefix'))
@@ -126,22 +94,13 @@ conf.set('SKIP_FRAMES', get_option('skip_frames'))
# enable High DPI support # enable High DPI support
conf.set('HIDPI_SUPPORT', get_option('hidpi_support')) conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
# disable console on Windows # enable audio support (enable audio forwarding with --forward-audio)
conf.set('WINDOWS_NOCONSOLE', get_option('windows_noconsole')) conf.set('AUDIO_SUPPORT', get_option('audio_support'))
configure_file(configuration: conf, output: 'config.h') configure_file(configuration: conf, output: 'config.h')
src_dir = include_directories('src') src_dir = include_directories('src')
executable('scrcpy', src, dependencies: dependencies, include_directories: src_dir, install: true)
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)
### TESTS ### TESTS

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

@@ -18,8 +18,7 @@ static inline const char *get_adb_command() {
return adb_command; return adb_command;
} }
process_t adb_execute(const char *serial, const char *const adb_cmd[], int len) { static void fill_cmd(const char *cmd[], const char *serial, const char *const adb_cmd[], int len) {
const char *cmd[len + 4];
int i; int i;
cmd[0] = get_adb_command(); cmd[0] = get_adb_command();
if (serial) { if (serial) {
@@ -32,9 +31,21 @@ process_t adb_execute(const char *serial, const char *const adb_cmd[], int len)
memcpy(&cmd[i], adb_cmd, len * sizeof(const char *)); memcpy(&cmd[i], adb_cmd, len * sizeof(const char *));
cmd[len + i] = NULL; cmd[len + i] = NULL;
}
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); 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) { process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
@@ -72,26 +83,45 @@ process_t adb_push(const char *serial, const char *local, const char *remote) {
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
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)
size_t len = strlen(local);
char quoted[len + 3];
memcpy(&quoted[1], local, len);
quoted[0] = '"';
quoted[len + 1] = '"';
quoted[len + 2] = '\0';
local = quoted;
#endif
const char *const adb_cmd[] = {"install", "-r", local};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
process_t adb_remove_path(const char *serial, const char *path) { process_t adb_remove_path(const char *serial, const char *path) {
const char *const adb_cmd[] = {"shell", "rm", path}; const char *const adb_cmd[] = {"shell", "rm", path};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); 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) { SDL_bool process_check_success(process_t proc, const char *name) {
if (proc == PROCESS_NONE) { if (proc == PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name); LOGE("Could not execute \"%s\"", name);

View File

@@ -24,29 +24,42 @@
# define PROCESS_NONE NULL # define PROCESS_NONE NULL
typedef HANDLE process_t; typedef HANDLE process_t;
typedef DWORD exit_code_t; typedef DWORD exit_code_t;
typedef HANDLE pipe_t;
#else #else
# include <sys/types.h> # include <sys/types.h>
# define PROCESS_NONE -1 # define PROCESS_NONE -1
typedef pid_t process_t; typedef pid_t process_t;
typedef int exit_code_t; typedef int exit_code_t;
typedef int pipe_t;
#endif #endif
# define NO_EXIT_CODE -1 # define NO_EXIT_CODE -1
process_t cmd_execute(const char *path, const char *const argv[]); 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_terminate(process_t pid);
SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code); 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(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(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_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(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_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_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); 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 // convenience function to wait for a successful process execution
// automatically log process errors with the provided process name // automatically log process errors with the provided process name
SDL_bool process_check_success(process_t process, const char *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 #endif

View File

@@ -1,7 +1,6 @@
#include "controlevent.h" #include "controlevent.h"
#include <SDL2/SDL_stdinc.h> #include <SDL2/SDL_stdinc.h>
#include <string.h>
#include "lockutil.h" #include "lockutil.h"
#include "log.h" #include "log.h"

View File

@@ -54,32 +54,27 @@ static SDL_bool process_event(struct controller *controller, const struct contro
static int run_controller(void *data) { static int run_controller(void *data) {
struct controller *controller = data; struct controller *controller = data;
for (;;) {
mutex_lock(controller->mutex); mutex_lock(controller->mutex);
for (;;) {
while (!controller->stopped && control_event_queue_is_empty(&controller->queue)) { while (!controller->stopped && control_event_queue_is_empty(&controller->queue)) {
cond_wait(controller->event_cond, controller->mutex); cond_wait(controller->event_cond, controller->mutex);
} }
if (controller->stopped) { if (controller->stopped) {
// stop immediately, do not process further events // stop immediately, do not process further events
mutex_unlock(controller->mutex);
break; break;
} }
struct control_event event; struct control_event event;
#ifdef BUILD_DEBUG while (control_event_queue_take(&controller->queue, &event)) {
bool non_empty = control_event_queue_take(&controller->queue, &event);
SDL_assert(non_empty);
#else
control_event_queue_take(&controller->queue, &event);
#endif
mutex_unlock(controller->mutex);
SDL_bool ok = process_event(controller, &event); SDL_bool ok = process_event(controller, &event);
control_event_destroy(&event); control_event_destroy(&event);
if (!ok) { if (!ok) {
LOGD("Cannot write event to socket"); LOGD("Cannot write event to socket");
break; goto end;
} }
} }
}
end:
mutex_unlock(controller->mutex);
return 0; return 0;
} }

View File

@@ -29,58 +29,52 @@ static struct point get_mouse_point(struct screen *screen) {
}; };
} }
static const int ACTION_DOWN = 1; static void send_keycode(struct controller *controller, enum android_keycode keycode, const char *name) {
static const int ACTION_UP = 1 << 1;
static void send_keycode(struct controller *controller, enum android_keycode keycode, int actions, const char *name) {
// send DOWN event // send DOWN event
struct control_event control_event; struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_KEYCODE; 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.keycode = keycode;
control_event.keycode_event.metastate = 0; 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)) { if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot send %s (DOWN)", name); LOGW("Cannot send %s (DOWN)", name);
return; return;
} }
}
if (actions & ACTION_UP) { // send UP event
control_event.keycode_event.action = AKEY_EVENT_ACTION_UP; control_event.keycode_event.action = AKEY_EVENT_ACTION_UP;
if (!controller_push_event(controller, &control_event)) { if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot send %s (UP)", name); LOGW("Cannot send %s (UP)", name);
} }
} }
static inline void action_home(struct controller *controller) {
send_keycode(controller, AKEYCODE_HOME, "HOME");
} }
static inline void action_home(struct controller *controller, int actions) { static inline void action_back(struct controller *controller) {
send_keycode(controller, AKEYCODE_HOME, actions, "HOME"); send_keycode(controller, AKEYCODE_BACK, "BACK");
} }
static inline void action_back(struct controller *controller, int actions) { static inline void action_app_switch(struct controller *controller) {
send_keycode(controller, AKEYCODE_BACK, actions, "BACK"); send_keycode(controller, AKEYCODE_APP_SWITCH, "APP_SWITCH");
} }
static inline void action_app_switch(struct controller *controller, int actions) { static inline void action_power(struct controller *controller) {
send_keycode(controller, AKEYCODE_APP_SWITCH, actions, "APP_SWITCH"); send_keycode(controller, AKEYCODE_POWER, "POWER");
} }
static inline void action_power(struct controller *controller, int actions) { static inline void action_volume_up(struct controller *controller) {
send_keycode(controller, AKEYCODE_POWER, actions, "POWER"); send_keycode(controller, AKEYCODE_VOLUME_UP, "VOLUME_UP");
} }
static inline void action_volume_up(struct controller *controller, int actions) { static inline void action_volume_down(struct controller *controller) {
send_keycode(controller, AKEYCODE_VOLUME_UP, actions, "VOLUME_UP"); send_keycode(controller, AKEYCODE_VOLUME_DOWN, "VOLUME_DOWN");
} }
static inline void action_volume_down(struct controller *controller, int actions) { static inline void action_menu(struct controller *controller) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, actions, "VOLUME_DOWN"); send_keycode(controller, AKEYCODE_MENU, "MENU");
}
static inline void action_menu(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
} }
// turn the screen on if it was off, press BACK otherwise // turn the screen on if it was off, press BACK otherwise
@@ -147,74 +141,57 @@ void input_manager_process_key(struct input_manager *input_manager,
// capture all Ctrl events // capture all Ctrl events
if (ctrl) { 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); SDL_bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
if (shift) { if (shift) {
// currently, there is no shortcut involving SHIFT // currently, there is no shortcut implying SHIFT
return; return;
} }
SDL_Keycode keycode = event->keysym.sym; SDL_Keycode keycode = event->keysym.sym;
int action = event->type == SDL_KEYDOWN ? ACTION_DOWN : ACTION_UP;
SDL_bool repeat = event->repeat;
switch (keycode) { switch (keycode) {
case SDLK_h: case SDLK_h:
if (!repeat) { action_home(input_manager->controller);
action_home(input_manager->controller, action);
}
return; return;
case SDLK_b: // fall-through case SDLK_b: // fall-through
case SDLK_BACKSPACE: case SDLK_BACKSPACE:
if (!repeat) { action_back(input_manager->controller);
action_back(input_manager->controller, action);
}
return; return;
case SDLK_s: case SDLK_s:
if (!repeat) { action_app_switch(input_manager->controller);
action_app_switch(input_manager->controller, action);
}
return; return;
case SDLK_m: case SDLK_m:
if (!repeat) { action_menu(input_manager->controller);
action_menu(input_manager->controller, action);
}
return; return;
case SDLK_p: case SDLK_p:
if (!repeat) { action_power(input_manager->controller);
action_power(input_manager->controller, action);
}
return; return;
case SDLK_DOWN: case SDLK_DOWN:
// forward repeated events action_volume_down(input_manager->controller);
action_volume_down(input_manager->controller, action);
return; return;
case SDLK_UP: case SDLK_UP:
// forward repeated events action_volume_up(input_manager->controller);
action_volume_up(input_manager->controller, action);
return; return;
case SDLK_v: case SDLK_v:
if (!repeat && event->type == SDL_KEYDOWN) {
clipboard_paste(input_manager->controller); clipboard_paste(input_manager->controller);
}
return; return;
case SDLK_f: case SDLK_f:
if (!repeat && event->type == SDL_KEYDOWN) {
screen_switch_fullscreen(input_manager->screen); screen_switch_fullscreen(input_manager->screen);
}
return; return;
case SDLK_x: case SDLK_x:
if (!repeat && event->type == SDL_KEYDOWN) {
screen_resize_to_fit(input_manager->screen); screen_resize_to_fit(input_manager->screen);
}
return; return;
case SDLK_g: case SDLK_g:
if (!repeat && event->type == SDL_KEYDOWN) {
screen_resize_to_pixel_perfect(input_manager->screen); screen_resize_to_pixel_perfect(input_manager->screen);
}
return; return;
case SDLK_i: case SDLK_i:
if (!repeat && event->type == SDL_KEYDOWN) {
switch_fps_counter_state(input_manager->frames); switch_fps_counter_state(input_manager->frames);
}
return; return;
} }
@@ -251,7 +228,7 @@ void input_manager_process_mouse_button(struct input_manager *input_manager,
return; return;
} }
if (event->button == SDL_BUTTON_MIDDLE) { if (event->button == SDL_BUTTON_MIDDLE) {
action_home(input_manager->controller, ACTION_DOWN | ACTION_UP); action_home(input_manager->controller);
return; return;
} }
// double-click on black borders resize to fit the device screen // double-click on black borders resize to fit the device screen

View File

@@ -1,180 +0,0 @@
#include "installer.h"
#include <string.h>
#include "command.h"
#include "lockutil.h"
#include "log.h"
// NOTE(adopi) this can be more generic:
// it could be used with a command queue instead of a filename queue
// then we would have a generic invoker (useful if we want to handle more async commands)
SDL_bool apk_queue_is_empty(const struct apk_queue *queue) {
return queue->head == queue->tail;
}
SDL_bool apk_queue_is_full(const struct apk_queue *queue) {
return (queue->head + 1) % APK_QUEUE_SIZE == queue->tail;
}
SDL_bool apk_queue_init(struct apk_queue *queue) {
queue->head = 0;
queue->tail = 0;
return SDL_TRUE;
}
void apk_queue_destroy(struct apk_queue *queue) {
int i = queue->tail;
while (i != queue->head) {
SDL_free(queue->data[i]);
i = (i + 1) % APK_QUEUE_SIZE;
}
}
SDL_bool apk_queue_push(struct apk_queue *queue, const char *apk) {
if (apk_queue_is_full(queue)) {
return SDL_FALSE;
}
queue->data[queue->head] = SDL_strdup(apk);
queue->head = (queue->head + 1) % APK_QUEUE_SIZE;
return SDL_TRUE;
}
SDL_bool apk_queue_take(struct apk_queue *queue, char **apk) {
if (apk_queue_is_empty(queue)) {
return SDL_FALSE;
}
// transfer ownership
*apk = queue->data[queue->tail];
queue->tail = (queue->tail + 1) % APK_QUEUE_SIZE;
return SDL_TRUE;
}
SDL_bool installer_init(struct installer *installer, const char *serial) {
if (!apk_queue_init(&installer->queue)) {
return SDL_FALSE;
}
if (!(installer->mutex = SDL_CreateMutex())) {
return SDL_FALSE;
}
if (!(installer->event_cond = SDL_CreateCond())) {
SDL_DestroyMutex(installer->mutex);
return SDL_FALSE;
}
if (serial) {
installer->serial = SDL_strdup(serial);
if (!installer->serial) {
LOGW("Cannot strdup serial");
return SDL_FALSE;
}
} else {
installer->serial = NULL;
}
// lazy initialization
installer->initialized = SDL_FALSE;
installer->stopped = SDL_FALSE;
installer->current_process = PROCESS_NONE;
return SDL_TRUE;
}
void installer_destroy(struct installer *installer) {
SDL_DestroyCond(installer->event_cond);
SDL_DestroyMutex(installer->mutex);
apk_queue_destroy(&installer->queue);
SDL_free((void *) installer->serial);
}
SDL_bool installer_install_apk(struct installer *installer, const char *apk) {
SDL_bool res;
// start installer if it's used for the first time
if (!installer->initialized) {
if (!installer_start(installer)) {
return SDL_FALSE;
}
installer->initialized = SDL_TRUE;
}
mutex_lock(installer->mutex);
SDL_bool was_empty = apk_queue_is_empty(&installer->queue);
res = apk_queue_push(&installer->queue, apk);
if (was_empty) {
cond_signal(installer->event_cond);
}
mutex_unlock(installer->mutex);
return res;
}
static int run_installer(void *data) {
struct installer *installer = data;
for (;;) {
mutex_lock(installer->mutex);
while (!installer->stopped && apk_queue_is_empty(&installer->queue)) {
cond_wait(installer->event_cond, installer->mutex);
}
if (installer->stopped) {
// stop immediately, do not process further events
mutex_unlock(installer->mutex);
break;
}
char *current_apk;
#ifdef BUILD_DEBUG
bool non_empty = apk_queue_take(&installer->queue, &current_apk);
SDL_assert(non_empty);
#else
apk_queue_take(&installer->queue, &current_apk);
#endif
LOGI("Installing %s...", current_apk);
process_t process = adb_install(installer->serial, current_apk);
installer->current_process = process;
mutex_unlock(installer->mutex);
if (process_check_success(process, "adb install")) {
LOGI("%s installed successfully", current_apk);
} else {
LOGE("Failed to install %s", current_apk);
}
SDL_free(current_apk);
}
return 0;
}
SDL_bool installer_start(struct installer *installer) {
LOGD("Starting installer thread");
installer->thread = SDL_CreateThread(run_installer, "installer", installer);
if (!installer->thread) {
LOGC("Could not start installer thread");
return SDL_FALSE;
}
return SDL_TRUE;
}
void installer_stop(struct installer *installer) {
mutex_lock(installer->mutex);
installer->stopped = SDL_TRUE;
cond_signal(installer->event_cond);
if (installer->current_process != PROCESS_NONE) {
if (!cmd_terminate(installer->current_process)) {
LOGW("Cannot terminate install process");
}
cmd_simple_wait(installer->current_process, NULL);
installer->current_process = PROCESS_NONE;
}
mutex_unlock(installer->mutex);
}
void installer_join(struct installer *installer) {
SDL_WaitThread(installer->thread, NULL);
}

View File

@@ -1,40 +0,0 @@
#ifndef APK_INSTALLER_H
#define APK_INSTALLER_H
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_thread.h>
#include "command.h"
#define APK_QUEUE_SIZE 16
// NOTE(AdoPi) apk_queue and control_event can use a generic queue
struct apk_queue {
char *data[APK_QUEUE_SIZE];
int tail;
int head;
};
struct installer {
const char *serial;
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *event_cond;
SDL_bool stopped;
SDL_bool initialized;
process_t current_process;
struct apk_queue queue;
};
SDL_bool installer_init(struct installer *installer, const char *serial);
void installer_destroy(struct installer *installer);
SDL_bool installer_start(struct installer *installer);
void installer_stop(struct installer *installer);
void installer_join(struct installer *installer);
// install an apk
SDL_bool installer_install_apk(struct installer *installer, const char *filename);
#endif

View File

@@ -10,10 +10,12 @@
struct args { struct args {
const char *serial; const char *serial;
const char *crop;
SDL_bool help; SDL_bool help;
SDL_bool version; SDL_bool version;
SDL_bool show_touches; SDL_bool show_touches;
#ifdef AUDIO_SUPPORT
SDL_bool forward_audio;
#endif
Uint16 port; Uint16 port;
Uint16 max_size; Uint16 max_size;
Uint32 bit_rate; Uint32 bit_rate;
@@ -24,18 +26,18 @@ static void usage(const char *arg0) {
"Usage: %s [options]\n" "Usage: %s [options]\n"
"\n" "\n"
"Options:\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" "\n"
" -b, --bit-rate value\n" " -b, --bit-rate value\n"
" Encode the video at the given bit-rate, expressed in bits/s.\n" " Encode the video at the given bit-rate, expressed in bits/s.\n"
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" " Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
" Default is %d.\n" " Default is %d.\n"
"\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"
" -h, --help\n" " -h, --help\n"
" Print this help.\n" " Print this help.\n"
"\n" "\n"
@@ -105,9 +107,6 @@ static void usage(const char *arg0) {
"\n" "\n"
" Ctrl+i\n" " Ctrl+i\n"
" enable/disable FPS counter (print frames/second in logs)\n" " enable/disable FPS counter (print frames/second in logs)\n"
"\n"
" Drag & drop APK file\n"
" install APK from computer\n"
"\n", "\n",
arg0, arg0,
DEFAULT_BIT_RATE, DEFAULT_BIT_RATE,
@@ -198,8 +197,10 @@ static SDL_bool parse_port(char *optarg, Uint16 *port) {
static SDL_bool parse_args(struct args *args, int argc, char *argv[]) { static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
static const struct option long_options[] = { static const struct option long_options[] = {
#ifdef AUDIO_SUPPORT
{"forward-audio", no_argument, NULL, 'a'},
#endif
{"bit-rate", required_argument, NULL, 'b'}, {"bit-rate", required_argument, NULL, 'b'},
{"crop", required_argument, NULL, 'c'},
{"help", no_argument, NULL, 'h'}, {"help", no_argument, NULL, 'h'},
{"max-size", required_argument, NULL, 'm'}, {"max-size", required_argument, NULL, 'm'},
{"port", required_argument, NULL, 'p'}, {"port", required_argument, NULL, 'p'},
@@ -209,16 +210,23 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
{NULL, 0, NULL, 0 }, {NULL, 0, NULL, 0 },
}; };
int c; int c;
while ((c = getopt_long(argc, argv, "b:c:hm: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) { switch (c) {
#ifdef AUDIO_SUPPORT
case 'a':
args->forward_audio = SDL_TRUE;
break;
#endif
case 'b': case 'b':
if (!parse_bit_rate(optarg, &args->bit_rate)) { if (!parse_bit_rate(optarg, &args->bit_rate)) {
return SDL_FALSE; return SDL_FALSE;
} }
break; break;
case 'c':
args->crop = optarg;
break;
case 'h': case 'h':
args->help = SDL_TRUE; args->help = SDL_TRUE;
break; break;
@@ -264,13 +272,15 @@ int main(int argc, char *argv[]) {
#endif #endif
struct args args = { struct args args = {
.serial = NULL, .serial = NULL,
.crop = NULL,
.help = SDL_FALSE, .help = SDL_FALSE,
.version = SDL_FALSE, .version = SDL_FALSE,
.show_touches = SDL_FALSE, .show_touches = SDL_FALSE,
.port = DEFAULT_LOCAL_PORT, .port = DEFAULT_LOCAL_PORT,
.max_size = DEFAULT_MAX_SIZE, .max_size = DEFAULT_MAX_SIZE,
.bit_rate = DEFAULT_BIT_RATE, .bit_rate = DEFAULT_BIT_RATE,
#ifdef AUDIO_SUPPORT
.forward_audio = SDL_FALSE,
#endif
}; };
if (!parse_args(&args, argc, argv)) { if (!parse_args(&args, argc, argv)) {
return 1; return 1;
@@ -286,9 +296,7 @@ int main(int argc, char *argv[]) {
return 0; return 0;
} }
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
av_register_all(); av_register_all();
#endif
if (avformat_network_init()) { if (avformat_network_init()) {
return 1; return 1;
@@ -300,15 +308,19 @@ int main(int argc, char *argv[]) {
struct scrcpy_options options = { struct scrcpy_options options = {
.serial = args.serial, .serial = args.serial,
.crop = args.crop,
.port = args.port, .port = args.port,
.max_size = args.max_size, .max_size = args.max_size,
.bit_rate = args.bit_rate, .bit_rate = args.bit_rate,
.show_touches = args.show_touches, .show_touches = args.show_touches,
#ifdef AUDIO_SUPPORT
.forward_audio = args.forward_audio,
#endif
}; };
int res = scrcpy(&options) ? 0 : 1; int res = scrcpy(&options) ? 0 : 1;
avformat_network_deinit(); // ignore failure avformat_network_deinit(); // ignore failure
SDL_Quit();
return res; 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 net_send_all(socket_t socket, const void *buf, size_t len) {
ssize_t w = 0; ssize_t w;
while (len > 0) { while (len > 0) {
w = send(socket, buf, len, 0); w = send(socket, buf, len, 0);
if (w == -1) { if (w == -1) {

View File

@@ -7,6 +7,8 @@
#include <sys/time.h> #include <sys/time.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "aoa.h"
#include "audio.h"
#include "command.h" #include "command.h"
#include "common.h" #include "common.h"
#include "controller.h" #include "controller.h"
@@ -22,14 +24,16 @@
#include "screen.h" #include "screen.h"
#include "server.h" #include "server.h"
#include "tinyxpm.h" #include "tinyxpm.h"
#include "installer.h"
static struct server server = SERVER_INITIALIZER; static struct server server = SERVER_INITIALIZER;
static struct screen screen = SCREEN_INITIALIZER; static struct screen screen = SCREEN_INITIALIZER;
static struct frames frames; static struct frames frames;
static struct decoder decoder; static struct decoder decoder;
static struct controller controller; static struct controller controller;
static struct installer installer;
#ifdef AUDIO_SUPPORT
static struct audio_player audio_player;
#endif
static struct input_manager input_manager = { static struct input_manager input_manager = {
.controller = &controller, .controller = &controller,
@@ -104,9 +108,6 @@ static void event_loop(void) {
case SDL_MOUSEBUTTONUP: case SDL_MOUSEBUTTONUP:
input_manager_process_mouse_button(&input_manager, &event.button); input_manager_process_mouse_button(&input_manager, &event.button);
break; break;
case SDL_DROPFILE:
installer_install_apk(&installer, event.drop.file);
break;
} }
} }
} }
@@ -125,12 +126,11 @@ static void wait_show_touches(process_t process) {
} }
SDL_bool scrcpy(const struct scrcpy_options *options) { SDL_bool scrcpy(const struct scrcpy_options *options) {
if (!server_start(&server, options->serial, options->port, if (!SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1")) {
options->max_size, options->bit_rate, options->crop)) { LOGW("Cannot request to keep default signal handlers");
return SDL_FALSE;
} }
process_t proc_show_touches = PROCESS_NONE; process_t proc_show_touches;
SDL_bool show_touches_waited; SDL_bool show_touches_waited;
if (options->show_touches) { if (options->show_touches) {
LOGI("Enable show_touches"); LOGI("Enable show_touches");
@@ -138,13 +138,23 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
show_touches_waited = SDL_FALSE; 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; SDL_bool ret = SDL_TRUE;
if (!SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1")) { if (!server_start(&server, options->serial, options->port,
LOGW("Cannot request to keep default signal handlers"); 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; ret = SDL_FALSE;
goto finally_destroy_server; goto finally_destroy_server;
} }
@@ -174,12 +184,6 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
goto finally_destroy_server; goto finally_destroy_server;
} }
if (!installer_init(&installer, server.serial)) {
ret = SDL_FALSE;
server_stop(&server);
goto finally_destroy_frames;
}
decoder_init(&decoder, &frames, device_socket); decoder_init(&decoder, &frames, device_socket);
// now we consumed the header values, the socket receives the video stream // now we consumed the header values, the socket receives the video stream
@@ -187,7 +191,7 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
if (!decoder_start(&decoder)) { if (!decoder_start(&decoder)) {
ret = SDL_FALSE; ret = SDL_FALSE;
server_stop(&server); server_stop(&server);
goto finally_destroy_installer; goto finally_destroy_frames;
} }
if (!controller_init(&controller, device_socket)) { if (!controller_init(&controller, device_socket)) {
@@ -225,10 +229,6 @@ finally_stop_decoder:
// stop the server before decoder_join() to wake up the decoder // stop the server before decoder_join() to wake up the decoder
server_stop(&server); server_stop(&server);
decoder_join(&decoder); decoder_join(&decoder);
finally_destroy_installer:
installer_stop(&installer);
installer_join(&installer);
installer_destroy(&installer);
finally_destroy_frames: finally_destroy_frames:
frames_destroy(&frames); frames_destroy(&frames);
finally_destroy_server: finally_destroy_server:
@@ -243,6 +243,12 @@ finally_destroy_server:
} }
server_destroy(&server); server_destroy(&server);
finally_disable_audio_forwarding:
#ifdef AUDIO_SUPPORT
if (options->forward_audio) {
audio_forwarding_stop(&audio_player);
}
#endif
return ret; return ret;
} }

View File

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

View File

@@ -10,14 +10,12 @@
#define DISPLAY_MARGINS 96 #define DISPLAY_MARGINS 96
SDL_bool sdl_init_and_configure(void) { SDL_bool sdl_video_init(void) {
if (SDL_Init(SDL_INIT_VIDEO)) { if (SDL_InitSubSystem(SDL_INIT_VIDEO)) {
LOGC("Could not initialize SDL: %s", SDL_GetError()); LOGC("Could not initialize SDL video: %s", SDL_GetError());
return SDL_FALSE; return SDL_FALSE;
} }
atexit(SDL_Quit);
// Use the best available scale quality // Use the best available scale quality
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2")) { if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2")) {
LOGW("Could not enable bilinear filtering"); LOGW("Could not enable bilinear filtering");

View File

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

View File

@@ -3,7 +3,6 @@
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
#include <SDL2/SDL_assert.h> #include <SDL2/SDL_assert.h>
#include <SDL2/SDL_timer.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, static process_t execute_server(const char *serial,
Uint16 max_size, Uint32 bit_rate, Uint16 max_size, Uint32 bit_rate, SDL_bool tunnel_forward) {
const char *crop, SDL_bool tunnel_forward) {
char max_size_string[6]; char max_size_string[6];
char bit_rate_string[11]; char bit_rate_string[11];
sprintf(max_size_string, "%"PRIu16, max_size); sprintf(max_size_string, "%"PRIu16, max_size);
@@ -92,7 +90,6 @@ static process_t execute_server(const char *serial,
max_size_string, max_size_string,
bit_rate_string, bit_rate_string,
tunnel_forward ? "true" : "false", tunnel_forward ? "true" : "false",
crop ? crop : "",
}; };
return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0])); 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, 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; server->local_port = local_port;
if (serial) { if (serial) {
server->serial = SDL_strdup(serial); server->serial = SDL_strdup(serial);
if (!server->serial) {
return SDL_FALSE;
}
} }
if (!push_server(serial)) { if (!push_server(serial)) {
SDL_free((void *) server->serial);
return SDL_FALSE; return SDL_FALSE;
} }
server->server_copied_to_device = SDL_TRUE; server->server_copied_to_device = SDL_TRUE;
if (!enable_tunnel(server)) { if (!enable_tunnel(server)) {
SDL_free((void *) server->serial);
return SDL_FALSE; 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) { if (server->server_socket == INVALID_SOCKET) {
LOGE("Could not listen on port %" PRIu16, local_port); LOGE("Could not listen on port %" PRIu16, local_port);
disable_tunnel(server); disable_tunnel(server);
SDL_free((void *) server->serial);
return SDL_FALSE; return SDL_FALSE;
} }
} }
// server will connect to our server socket // server will connect to our server socket
server->process = execute_server(serial, max_size, bit_rate, crop, server->process = execute_server(serial, max_size, bit_rate, server->tunnel_forward);
server->tunnel_forward);
if (server->process == PROCESS_NONE) { if (server->process == PROCESS_NONE) {
if (!server->tunnel_forward) { if (!server->tunnel_forward) {
close_socket(&server->server_socket); close_socket(&server->server_socket);
} }
disable_tunnel(server); disable_tunnel(server);
SDL_free((void *) server->serial);
return SDL_FALSE; return SDL_FALSE;
} }
@@ -210,7 +199,7 @@ socket_t server_connect_to(struct server *server) {
if (!server->tunnel_forward) { if (!server->tunnel_forward) {
server->device_socket = net_accept(server->server_socket); server->device_socket = net_accept(server->server_socket);
} else { } else {
Uint32 attempts = 100; Uint32 attempts = 50;
Uint32 delay = 100; // ms Uint32 delay = 100; // ms
server->device_socket = connect_to_server(server->local_port, attempts, delay); 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 // push, enable tunnel et start the server
SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port, 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 // block until the communication with the server is established
socket_t server_connect_to(struct server *server); socket_t server_connect_to(struct server *server);

View File

@@ -1,11 +1,9 @@
#include "command.h" #include "command.h"
#include <signal.h> #include <signal.h>
#include <stdlib.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#include "log.h"
pid_t cmd_execute(const char *path, const char *const argv[]) { pid_t cmd_execute(const char *path, const char *const argv[]) {
pid_t pid = fork(); pid_t pid = fork();
@@ -21,11 +19,94 @@ pid_t cmd_execute(const char *path, const char *const argv[]) {
return pid; return pid;
} }
SDL_bool cmd_terminate(pid_t pid) { pid_t cmd_execute_redirect(const char *path, const char *const argv[],
if (pid <= 0) { int *pipe_stdin, int *pipe_stdout, int *pipe_stderr) {
LOGC("Requested to kill %d, this is an error. Please report the bug.\n", (int) pid); int in[2];
abort(); 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;
}
}
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]);
}
close(in[1]);
}
if (pipe_stdout) {
if (out[1] != STDOUT_FILENO) {
dup2(out[1], STDOUT_FILENO);
close(out[1]);
}
close(out[0]);
}
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);
}
if (pipe_stdin) {
close(in[0]);
*pipe_stdin = in[1];
}
if (pipe_stdout) {
*pipe_stdout = out[0];
close(out[1]);
}
if (pipe_stderr) {
*pipe_stderr = err[0];
close(err[1]);
}
return pid;
}
SDL_bool cmd_terminate(pid_t pid) {
return kill(pid, SIGTERM) != -1; return kill(pid, SIGTERM) != -1;
} }
@@ -43,3 +124,13 @@ SDL_bool cmd_simple_wait(pid_t pid, int *exit_code) {
} }
return !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,32 +1,102 @@
#include "command.h" #include "command.h"
#include "config.h"
#include "log.h" #include "log.h"
#include "strutil.h" #include "strutil.h"
static int build_cmd(char *cmd, size_t len, const char *const argv[]) {
// Windows command-line parsing is WTF:
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
// only make it work for this very specific program
// (don't handle escaping nor quotes)
size_t ret = xstrjoin(cmd, argv, ' ', len);
if (ret >= len) {
LOGE("Command too long (%" PRIsizet " chars)", len - 1);
return -1;
}
return 0;
}
HANDLE cmd_execute(const char *path, const char *const argv[]) { HANDLE cmd_execute(const char *path, const char *const argv[]) {
STARTUPINFO si; STARTUPINFO si;
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si)); memset(&si, 0, sizeof(si));
si.cb = sizeof(si); si.cb = sizeof(si);
// Windows command-line parsing is WTF:
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
// only make it work for this very specific program
// (don't handle escaping nor quotes)
char cmd[256]; char cmd[256];
size_t ret = xstrjoin(cmd, argv, ' ', sizeof(cmd)); if (build_cmd(cmd, sizeof(cmd), argv)) {
if (ret >= sizeof(cmd)) {
LOGE("Command too long (%" PRIsizet " chars)", sizeof(cmd) - 1);
return NULL; return NULL;
} }
#ifdef WINDOWS_NOCONSOLE if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
int flags = CREATE_NO_WINDOW; return NULL;
#else }
int flags = 0;
#endif return pi.hProcess;
if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi)) { }
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);
}
}
}
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 NULL;
} }
@@ -48,3 +118,17 @@ SDL_bool cmd_simple_wait(HANDLE handle, DWORD *exit_code) {
} }
return !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,5 +1,4 @@
#include <assert.h> #include <assert.h>
#include <string.h>
#include "controlevent.h" #include "controlevent.h"

View File

@@ -1,5 +1,4 @@
#include <assert.h> #include <assert.h>
#include <string.h>
#include "controlevent.h" #include "controlevent.h"

View File

@@ -7,7 +7,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { 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 // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // 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-win32-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.0-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-win64-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.0-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 distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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=c99') project('scrcpy', 'c', meson_version: '>= 0.37')
if get_option('build_app') if get_option('build_app')
subdir('app') subdir('app')

View File

@@ -1,8 +1,7 @@
option('build_app', type: 'boolean', value: true, description: 'Build the client') option('build_app', type: 'boolean', value: true, description: 'Build the client')
option('build_server', type: 'boolean', value: true, description: 'Build the server') 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('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('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('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('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-win32-shared.zip \
530c92df0ca14c35901b4b681847d62da3c50a0cc9b7ced37b04968f6b5c243d \
ffmpeg-4.0-win32-shared
prepare-ffmpeg-dev-win32:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.0-win32-dev.zip \
e2f5200b5e73c4d0abb9b89c4ffc0438f92a0aadc54c81cf57e18c81a9f11c6b \
ffmpeg-4.0-win32-dev
prepare-ffmpeg-shared-win64:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.0-win64-shared.zip \
8fe2d344463dbefc2db4239a4203a55ed0324faceaae57276a40c4fabda84c37 \
ffmpeg-4.0-win64-shared
prepare-ffmpeg-dev-win64:
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.0-win64-dev.zip \
facced738eabfc53fa92834dea8b24426f64db61298688fed480145945be07fa \
ffmpeg-4.0-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_r27.0.1-windows.zip \
880662adfb0d6911ff250b9e13930ae1a4110fc36d5866afd4f8f56d935f7939 \
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" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 27 targetSdkVersion 27
versionCode 4 versionCode 2
versionName "1.3" versionName "1.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {

View File

@@ -3,7 +3,6 @@ package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build; import android.os.Build;
import android.os.RemoteException; import android.os.RemoteException;
import android.view.IRotationWatcher; import android.view.IRotationWatcher;
@@ -21,7 +20,7 @@ public final class Device {
private RotationListener rotationListener; private RotationListener rotationListener;
public Device(Options options) { public Device(Options options) {
screenInfo = computeScreenInfo(options.getCrop(), options.getMaxSize()); screenInfo = computeScreenInfo(options.getMaxSize());
registerRotationWatcher(new IRotationWatcher.Stub() { registerRotationWatcher(new IRotationWatcher.Stub() {
@Override @Override
public void onRotationChanged(int rotation) throws RemoteException { public void onRotationChanged(int rotation) throws RemoteException {
@@ -41,40 +40,18 @@ public final class Device {
return screenInfo; 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") @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. // Compute the video size and the padding of the content inside this video.
// Principle: // Principle:
// - scale down the great side of the screen to maxSize (if necessary); // - scale down the great side of the screen to maxSize (if necessary);
// - scale down the other side so that the aspect ratio is preserved; // - 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) // - 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 DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo();
h &= ~7; 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 (maxSize > 0) {
if (BuildConfig.DEBUG && maxSize % 8 != 0) { if (BuildConfig.DEBUG && maxSize % 8 != 0) {
throw new AssertionError("Max size must be a multiple of 8"); throw new AssertionError("Max size must be a multiple of 8");
@@ -91,12 +68,12 @@ public final class Device {
w = portrait ? minor : major; w = portrait ? minor : major;
h = portrait ? major : minor; 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) { public Point getPhysicalPoint(Position position) {
// it hides the field on purpose, to read it with a lock @SuppressWarnings("checkstyle:HiddenField") // it hides the field on purpose, to read it with a lock
@SuppressWarnings("checkstyle:HiddenField")
ScreenInfo screenInfo = getScreenInfo(); // read with synchronization ScreenInfo screenInfo = getScreenInfo(); // read with synchronization
Size videoSize = screenInfo.getVideoSize(); Size videoSize = screenInfo.getVideoSize();
Size clientVideoSize = position.getScreenSize(); 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 // the device may have been rotated since the event was generated, so ignore the event
return null; return null;
} }
Rect contentRect = screenInfo.getContentRect(); Size deviceSize = screenInfo.getDeviceSize();
Point point = position.getPoint(); Point point = position.getPoint();
int scaledX = contentRect.left + point.x * contentRect.width() / videoSize.getWidth(); int scaledX = point.x * deviceSize.getWidth() / videoSize.getWidth();
int scaledY = contentRect.top + point.y * contentRect.height() / videoSize.getHeight(); int scaledY = point.y * deviceSize.getHeight() / videoSize.getHeight();
return new Point(scaledX, scaledY); return new Point(scaledX, scaledY);
} }
@@ -131,8 +108,4 @@ public final class Device {
public synchronized void setRotationListener(RotationListener rotationListener) { public synchronized void setRotationListener(RotationListener rotationListener) {
this.rotationListener = rotationListener; this.rotationListener = rotationListener;
} }
static Rect flipRect(Rect crop) {
return new Rect(crop.top, crop.left, crop.bottom, crop.right);
}
} }

View File

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

View File

@@ -56,12 +56,12 @@ public class ScreenEncoder implements Device.RotationListener {
do { do {
MediaCodec codec = createCodec(); MediaCodec codec = createCodec();
IBinder display = createDisplay(); IBinder display = createDisplay();
Rect contentRect = device.getScreenInfo().getContentRect(); Rect deviceRect = device.getScreenInfo().getDeviceSize().toRect();
Rect videoRect = device.getScreenInfo().getVideoSize().toRect(); Rect videoRect = device.getScreenInfo().getVideoSize().toRect();
setSize(format, videoRect.width(), videoRect.height()); setSize(format, videoRect.width(), videoRect.height());
configure(codec, format); configure(codec, format);
Surface surface = codec.createInputSurface(); Surface surface = codec.createInputSurface();
setDisplaySurface(display, surface, contentRect, videoRect); setDisplaySurface(display, surface, deviceRect, videoRect);
codec.start(); codec.start();
try { try {
alive = encode(codec, outputStream); alive = encode(codec, outputStream);

View File

@@ -1,20 +1,18 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
import android.graphics.Rect;
public final class ScreenInfo { public final class ScreenInfo {
private final Rect contentRect; // device size, possibly cropped private final Size deviceSize;
private final Size videoSize; private final Size videoSize;
private final boolean rotated; private final boolean rotated;
public ScreenInfo(Rect contentRect, Size videoSize, boolean rotated) { public ScreenInfo(Size deviceSize, Size videoSize, boolean rotated) {
this.contentRect = contentRect; this.deviceSize = deviceSize;
this.videoSize = videoSize; this.videoSize = videoSize;
this.rotated = rotated; this.rotated = rotated;
} }
public Rect getContentRect() { public Size getDeviceSize() {
return contentRect; return deviceSize;
} }
public Size getVideoSize() { public Size getVideoSize() {
@@ -26,6 +24,6 @@ public final class ScreenInfo {
if (rotated == newRotated) { if (rotated == newRotated) {
return this; 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; package com.genymobile.scrcpy;
import android.graphics.Rect;
import java.io.IOException; import java.io.IOException;
public final class Server { public final class Server {
@@ -65,30 +63,8 @@ public final class Server {
boolean tunnelForward = Boolean.parseBoolean(args[2]); boolean tunnelForward = Boolean.parseBoolean(args[2]);
options.setTunnelForward(tunnelForward); options.setTunnelForward(tunnelForward);
if (args.length < 4) {
return options; 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 { public static void main(String... args) throws Exception {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {