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
63 changed files with 1091 additions and 1203 deletions

23
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:

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

107
README.md
View File

@@ -1,8 +1,8 @@
# scrcpy (v1.3)
# 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)
@@ -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
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).
The client requires [FFmpeg] and [LibSDL2]. On Windows, they are included in the
[prebuilt application](#windows).
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
[libsdl2]: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
[libusb]: https://en.wikipedia.org/wiki/Libusb
## Build and install
@@ -40,12 +45,12 @@ Install the required packages from your package manager.
```bash
# 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
sudo apt install make gcc pkg-config meson \
libavcodec-dev libavformat-dev libavutil-dev \
libsdl2-dev
libsdl2-dev libusb-1.0-0-dev
# server build dependencies
sudo apt install openjdk-8-jdk
@@ -76,54 +81,18 @@ Two [AUR] packages have been created by users:
#### Windows
For Windows, for simplicity, prebuilt archives with all the dependencies
(including `adb`) are available:
For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available:
- [`scrcpy-win32-v1.3.zip`][direct-win32].
_(SHA-256: 51a2990e631ed469a7a86ff38107d517a91d313fb3f8327eb7bc71dde40870b5)_
- [`scrcpy-win64-v1.3.zip`][direct-win64].
_(SHA-256: 0768a80d3d600d0bbcd220ca150ae88a3a58d1fe85c308a8c61f44480b711e43)_
- [`scrcpy-windows-with-deps-v1.1.zip`][direct-windows-with-deps].
_(SHA-256: 27eb36c15937601d1062c1dc0b45faae0e06fefea2019aadeb4fa7f76a07bb4c)_
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.3/scrcpy-win32-v1.3.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.3/scrcpy-win64-v1.3.zip
[direct-windows-with-deps]: https://github.com/Genymobile/scrcpy/releases/download/v1.1/scrcpy-windows-with-deps-v1.1.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
the following files to a directory accessible from your `PATH`:
- `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:
Instead, you may want to build it manually. You need [MSYS2] to build the
project. From an MSYS2 terminal, install the required packages:
[MSYS2]: http://www.msys2.org/
@@ -136,7 +105,8 @@ pacman -S mingw-w64-x86_64-SDL2 \
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-meson \
mingw-w64-x86_64-libusb
```
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
# 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
@@ -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
instead:
- [`scrcpy-server-v1.3.jar`][direct-scrcpy-server].
_(SHA-256: 0f9a5a217f33f0ed7a1498ceb3c0cccf31c53533893aa952e674c1571d2740c1)_
- [`scrcpy-server-v1.1.jar`][direct-scrcpy-server].
_(SHA-256: 14826512bf38447ec94adf3b531676ce038d19e7e06757fb4e537882b17e77b3)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.3/scrcpy-server-v1.3.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.
@@ -294,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
@@ -312,10 +276,10 @@ To show physical touches while scrcpy is running:
scrcpy -t
```
The app may be started directly in fullscreen:
To enable audio forwarding:
```
scrcpy -f
```bash
scrcpy -a
```
To run without installing:
@@ -344,8 +308,6 @@ To run without installing:
| 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._
@@ -391,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.3')
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

@@ -18,25 +18,8 @@ static inline const char *get_adb_command() {
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";
@@ -48,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) {
@@ -93,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));
}
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) {
const char *const adb_cmd[] = {"shell", "rm", 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

@@ -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,7 +1,7 @@
#include "input_manager.h"
#include "inputmanager.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)
@@ -29,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
@@ -147,74 +141,57 @@ void input_manager_process_key(struct input_manager *input_manager,
// capture all Ctrl events
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 (!repeat) {
action_home(input_manager->controller, action);
}
action_home(input_manager->controller);
return;
case SDLK_b: // fall-through
case SDLK_BACKSPACE:
if (!repeat) {
action_back(input_manager->controller, action);
}
action_back(input_manager->controller);
return;
case SDLK_s:
if (!repeat) {
action_app_switch(input_manager->controller, action);
}
action_app_switch(input_manager->controller);
return;
case SDLK_m:
if (!repeat) {
action_menu(input_manager->controller, action);
}
action_menu(input_manager->controller);
return;
case SDLK_p:
if (!repeat) {
action_power(input_manager->controller, action);
}
action_power(input_manager->controller);
return;
case SDLK_DOWN:
// forward repeated events
action_volume_down(input_manager->controller, action);
action_volume_down(input_manager->controller);
return;
case SDLK_UP:
// forward repeated events
action_volume_up(input_manager->controller, action);
action_volume_up(input_manager->controller);
return;
case SDLK_v:
if (!repeat && event->type == SDL_KEYDOWN) {
clipboard_paste(input_manager->controller);
}
clipboard_paste(input_manager->controller);
return;
case SDLK_f:
if (!repeat && event->type == SDL_KEYDOWN) {
screen_switch_fullscreen(input_manager->screen);
}
screen_switch_fullscreen(input_manager->screen);
return;
case SDLK_x:
if (!repeat && event->type == SDL_KEYDOWN) {
screen_resize_to_fit(input_manager->screen);
}
screen_resize_to_fit(input_manager->screen);
return;
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;
case SDLK_i:
if (!repeat && event->type == SDL_KEYDOWN) {
switch_fps_counter_state(input_manager->frames);
}
switch_fps_counter_state(input_manager->frames);
return;
}
@@ -251,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
@@ -265,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,16 +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
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,14 +80,13 @@ static SDL_bool event_loop(void) {
screen_show_window(&screen);
}
if (!screen_update_frame(&screen, &frames)) {
return SDL_FALSE;
return;
}
break;
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_update_scale(&screen);
screen_render(&screen);
break;
}
@@ -110,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) {
@@ -139,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");
@@ -152,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;
}
@@ -188,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
@@ -201,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)) {
@@ -224,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);
@@ -243,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:
@@ -261,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");
@@ -132,17 +130,6 @@ static inline struct size get_initial_optimal_size(struct size frame_size) {
return get_optimal_size(frame_size, frame_size);
}
// apply hidpi scaling and call SDL_RenderSetLogicalSize
static inline SDL_bool render_set_scaled_logical_size(struct screen *screen, struct size size) {
int w, h, dw, dh;
SDL_GL_GetDrawableSize(screen->window, &w, &h);
SDL_GetWindowSize(screen->window, &dw, &dh);
// use 32 bits unsigned not to lose precision (width and height fit in 16 bits)
int scaled_x = (Uint32) size.width * (Uint32) w / (Uint32) dw;
int scaled_y = (Uint32) size.height * (Uint32) h / (Uint32) dh;
return SDL_RenderSetLogicalSize(screen->renderer, scaled_x, scaled_y);
}
void screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER;
}
@@ -174,7 +161,7 @@ SDL_bool screen_init_rendering(struct screen *screen, const char *device_name, s
return SDL_FALSE;
}
if (render_set_scaled_logical_size(screen, frame_size)) {
if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width, frame_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
screen_destroy(screen);
return SDL_FALSE;
@@ -219,7 +206,7 @@ void screen_destroy(struct screen *screen) {
// recreate the texture and resize the window if the frame size has changed
static SDL_bool prepare_for_frame(struct screen *screen, struct size new_frame_size) {
if (screen->frame_size.width != new_frame_size.width || screen->frame_size.height != new_frame_size.height) {
if (render_set_scaled_logical_size(screen, new_frame_size)) {
if (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width, new_frame_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
return SDL_FALSE;
}
@@ -313,7 +300,3 @@ void screen_resize_to_pixel_perfect(struct screen *screen) {
LOGD("Resized to pixel-perfect");
}
}
void screen_update_scale(struct screen *screen) {
render_set_scaled_logical_size(screen, screen->frame_size);
}

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);
@@ -66,8 +66,4 @@ void screen_resize_to_fit(struct screen *screen);
// resize window to 1:1 (pixel-perfect)
void screen_resize_to_pixel_perfect(struct screen *screen);
// recompute the scale in case the window moved from/to another screen with a
// different HiDPI
void screen_update_scale(struct screen *screen);
#endif

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,4 +1,4 @@
#include "str_util.h"
#include "strutil.h"
size_t xstrncpy(char *dest, const char *src, size_t n) {
size_t i;

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,42 +1,106 @@
#include "command.h"
#include "config.h"
#include "log.h"
#include "str_util.h"
#include "strutil.h"
enum process_result cmd_execute(const char *path, const char *const argv[], HANDLE *handle) {
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[]) {
STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si, 0, 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];
size_t ret = xstrjoin(cmd, argv, ' ', sizeof(cmd));
if (ret >= sizeof(cmd)) {
LOGE("Command too long (%" PRIsizet " chars)", sizeof(cmd) - 1);
*handle = NULL;
return PROCESS_ERROR_GENERIC;
if (build_cmd(cmd, sizeof(cmd), argv)) {
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) {
@@ -54,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,7 +1,6 @@
#include <assert.h>
#include <string.h>
#include "control_event.h"
#include "controlevent.h"
static void test_control_event_queue_empty() {
struct control_event_queue queue;

View File

@@ -1,7 +1,6 @@
#include <assert.h>
#include <string.h>
#include "control_event.h"
#include "controlevent.h"
static void test_serialize_keycode_event() {
struct control_event event = {

View File

@@ -1,7 +1,7 @@
#include <assert.h>
#include <string.h>
#include "str_util.h"
#include "strutil.h"
static void test_xstrncpy_simple() {
char s[] = "xxxxxxxxxx";

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.0-windows.zip \
e2c1ec7c8e9b71cf1c8befd3bff91d06b26dd334c3f32b3817e9d46ba260b0e8 \
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 4
versionName "1.3"
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,31 +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 {
while (from.hasRemaining()) {
try {
Os.write(fd, from);
} 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