Compare commits

..

2 Commits

Author SHA1 Message Date
Romain Vimont
e3a07c3309 update-on-event 2018-09-13 22:24:26 +02:00
Romain Vimont
2e40285244 hidpi-scale-wip 2018-09-13 20:37:45 +02:00
81 changed files with 1533 additions and 3439 deletions

251
BUILD.md
View File

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

View File

@@ -32,7 +32,7 @@ The server is a Java application (with a [`public static void main(String...
args)`][main] method), compiled against the Android framework, and executed as
`shell` on the Android device.
[main]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/Server.java#L100
[main]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/Server.java#L61
To run such a Java application, the classes must be [_dexed_][dex] (typically,
to `classes.dex`). If `my.package.MainClass` is the main class, compiled to
@@ -65,8 +65,8 @@ They can be called using reflection though. The communication with hidden
components is provided by [_wrappers_ classes][wrappers] and [aidl].
[hidden]: https://stackoverflow.com/a/31908373/1987178
[wrappers]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/wrappers
[aidl]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/aidl/android/view
[wrappers]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/wrappers
[aidl]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/aidl/android/view
### Threading
@@ -89,9 +89,9 @@ The video is encoded using the [`MediaCodec`] API. The codec takes its input
from a [surface] associated to the display, and writes the resulting H.264
stream to the provided output stream (the socket connected to the client).
[`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
[`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
[`MediaCodec`]: https://developer.android.com/reference/android/media/MediaCodec.html
[surface]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L69-L70
[surface]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L63-L64
On device [rotation], the codec, surface and display are reinitialized, and a
new video stream is produced.
@@ -105,9 +105,8 @@ because it avoids to send unnecessary frames, but there are drawbacks:
Both problems are [solved][repeat] by the flag
[`KEY_REPEAT_PREVIOUS_FRAME_AFTER`][repeat-flag].
[rotation]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L90
[repeat]:
https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L147-L148
[rotation]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L89-L92
[repeat]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L125-L126
[repeat-flag]: https://developer.android.com/reference/android/media/MediaFormat.html#KEY_REPEAT_PREVIOUS_FRAME_AFTER
@@ -125,11 +124,11 @@ All of them may need to inject input events to the system. To do so, they use
the _hidden_ method [`InputManager.injectInputEvent`] (exposed by our
[`InputManager` wrapper][inject-wrapper]).
[`EventController`]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/EventController.java#L66
[`EventController`]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/EventController.java#L70
[`KeyEvent`]: https://developer.android.com/reference/android/view/KeyEvent.html
[`MotionEvent`]: https://developer.android.com/reference/android/view/MotionEvent.html
[`InputManager.injectInputEvent`]: https://android.googlesource.com/platform/frameworks/base/+/oreo-release/core/java/android/hardware/input/InputManager.java#857
[inject-wrapper]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27
[inject-wrapper]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27
@@ -154,7 +153,7 @@ Note that the client-server roles are expressed at the application level:
- the server _serves_ video stream and handle requests from the client,
- the client _controls_ the device through the server.
However, the roles are reversed at the network level:
However, the roles are inverted at the network level:
- the client opens a server socket and listen on a port before starting the
server,
@@ -163,9 +162,6 @@ However, the roles are reversed at the network level:
This role inversion guarantees that the connection will not fail due to race
conditions, and avoids polling.
_(Note that over TCP/IP, the roles are not reversed, due to a bug in `adb
reverse`. See commit [1038bad] and [issue #5].)_
Once the server is connected, it sends the device information (name and initial
screen dimensions). Thus, the client may init the window and renderer, before
the first frame is available.
@@ -173,8 +169,6 @@ the first frame is available.
To minimize startup time, SDL initialization is performed while listening for
the connection from the server (see commit [90a46b4]).
[1038bad]: https://github.com/Genymobile/scrcpy/commit/1038bad3850f18717a048a4d5c0f8110e54ee172
[issue #5]: https://github.com/Genymobile/scrcpy/issues/5
[90a46b4]: https://github.com/Genymobile/scrcpy/commit/90a46b4c45637d083e877020d85ade52a9a5fa8e
@@ -183,25 +177,17 @@ the connection from the server (see commit [90a46b4]).
The client uses 3 threads:
- the **main** thread, executing the SDL event loop,
- the **stream** thread, receiving the video and used for decoding and
recording,
- the **decoder** thread, decoding video frames,
- the **controller** thread, sending _control events_ to the server.
In addition, another thread can be started if necessary to handle APK
installation or file push requests (via drag&drop on the main window).
### Decoder
The [decoder] runs in a separate thread. It uses _libav_ to decode the H.264
stream from the socket, and notifies the main thread when a new frame is
available.
### Stream
The video [stream] is received from the socket (connected to the server on the
device) in a separate thread.
If a [decoder] is present (i.e. `--no-display` is not set), then it uses _libav_
to decode the H.264 stream from the socket, and notifies the main thread when a
new frame is available.
There are two [frames][video_buffer] simultaneously in memory:
There are two [frames] simultaneously in memory:
- the **decoding** frame, written by the decoder from the decoder thread,
- the **rendering** frame, rendered in a texture from the main thread.
@@ -209,23 +195,9 @@ When a new decoded frame is available, the decoder _swaps_ the decoding and
rendering frame (with proper synchronization). Thus, it immediatly starts
to decode a new frame while the main thread renders the last one.
If a [recorder] is present (i.e. `--record` is enabled), then its muxes the raw
H.264 packet to the output video file.
[decoder]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/decoder.c
[frames]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/frames.h
[stream]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/stream.h
[decoder]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/decoder.h
[video_buffer]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/video_buffer.h
[recorder]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/recorder.h
```
+----------+ +----------+
---> | decoder | ---> | screen |
+---------+ / +----------+ +----------+
socket ---> | stream | ----
+---------+ \ +----------+
---> | recorder |
+----------+
```
### Controller
@@ -239,10 +211,10 @@ events_ to a blocking queue hold by the controller. On its own thread, the
controller takes events from the queue, that it serializes and sends to the
client.
[controller]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/controller.h
[controlevent]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/control_event.h
[inputmanager]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/input_manager.h
[convert]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/convert.h
[controller]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/controller.h
[controlevent]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/controlevent.h
[inputmanager]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/inputmanager.h
[convert]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/convert.h
### UI and event loop
@@ -253,10 +225,9 @@ thread.
Events are handled in the [event loop], which either updates the [screen] or
delegates to the [input manager][inputmanager].
[scrcpy]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/scrcpy.c
[event loop]:
https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/scrcpy.c#L187
[screen]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/screen.h
[scrcpy]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/scrcpy.c
[event loop]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/scrcpy.c#L38
[screen]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/screen.h
## Hack

13
FAQ.md
View File

@@ -24,6 +24,19 @@ If you still encounter problems, please see [issue 9].
[issue 9]: https://github.com/Genymobile/scrcpy/issues/9
### I get a black screen for some applications like Silence
This is expected, they requested to protect the screen.
In [Silence], you can disable it in settings → Privacy → Screen security.
[silence]: https://f-droid.org/en/packages/org.smssecure.smssecure/
See [issue 36].
[issue 36]: https://github.com/Genymobile/scrcpy/issues/36
### Mouse clicks do not work
On some devices, you may need to enable an option to allow [simulating input].

View File

@@ -34,7 +34,7 @@ WIN32_TARGET := $(WIN32_TARGET_DIR)-$(VERSION).zip
WIN64_TARGET := $(WIN64_TARGET_DIR)-$(VERSION).zip
release: clean zip-win32 zip-win64 sums
@echo "Windows archives generated in $(DIST)/"
@echo "Release created in $(DIST)/."
clean:
$(GRADLE) clean
@@ -100,28 +100,28 @@ dist-win32: build-server build-win32 build-win32-noconsole
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.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.1-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
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.9/i686-w64-mingw32/bin/SDL2.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.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.1-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
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.9/x86_64-w64-mingw32/bin/SDL2.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)"; \

445
README.md
View File

@@ -1,4 +1,4 @@
# scrcpy (v1.8)
# scrcpy (v1.3)
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.
@@ -11,52 +11,156 @@ It works on _GNU/Linux_, _Windows_ and _MacOS_.
The Android part requires at least API 21 (Android 5.0).
You need [adb]. It is available in the [Android SDK platform
tools][platform-tools], or packaged in your distribution (`android-adb-tools`).
On Windows, just [download scrcpy for Windows](#windows), `adb` is included.
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).
[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
On some devices, you also need to enable [an additional option][control] to
control it using keyboard and mouse.
## Build and install
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
### System-specific steps
#### Linux
## Get the app
Install the required packages from your package manager.
##### Debian/Ubuntu
### Linux
```bash
# runtime dependencies
sudo apt install ffmpeg libsdl2-2.0.0
On Linux, you typically need to [build the app manually][BUILD]. Don't worry,
it's not that hard.
# client build dependencies
sudo apt install make gcc pkg-config meson \
libavcodec-dev libavformat-dev libavutil-dev \
libsdl2-dev
For Arch Linux, an [AUR] package is available: [`scrcpy`][aur-link].
# server build dependencies
sudo apt install openjdk-8-jdk
```
##### Fedora
```bash
# enable RPM fusion free
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
# client build dependencies
sudo dnf install SDL2-devel ffms2-devel meson gcc make
# server build dependencies
sudo dnf install java
```
##### Arch Linux
Two [AUR] packages have been created by users:
- [`scrcpy`](https://aur.archlinux.org/packages/scrcpy/)
- [`scrcpy-prebuiltserver`](https://aur.archlinux.org/packages/scrcpy-prebuiltserver/)
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
### Windows
#### Windows
For Windows, for simplicity, prebuilt archives with all the dependencies
(including `adb`) are available:
- [`scrcpy-win32-v1.8.zip`][direct-win32]
_(SHA-256: c0c29ed1c66deaa73bdadacd09e598aafb3a117929cf7a314cce1cc45e34de53)_
- [`scrcpy-win64-v1.8.zip`][direct-win64]
_(SHA-256: 9cc980d07bd8f036ae4e91d0bc6fc3281d7fa8f9752d4913b643c0fb72a19fb7)_
- [`scrcpy-win32-v1.3.zip`][direct-win32].
_(SHA-256: 51a2990e631ed469a7a86ff38107d517a91d313fb3f8327eb7bc71dde40870b5)_
- [`scrcpy-win64-v1.3.zip`][direct-win64].
_(SHA-256: 0768a80d3d600d0bbcd220ca150ae88a3a58d1fe85c308a8c61f44480b711e43)_
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.8/scrcpy-win32-v1.8.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.8/scrcpy-win64-v1.8.zip
[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
You can also [build the app manually][BUILD].
Instead, you may want to build it manually.
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/`.
### Mac OS
##### In MSYS2
From Windows, you need [MSYS2] to build the project. From an MSYS2 terminal,
install the required packages:
[MSYS2]: http://www.msys2.org/
```bash
# runtime dependencies
pacman -S mingw-w64-x86_64-SDL2 \
mingw-w64-x86_64-ffmpeg
# client build dependencies
pacman -S mingw-w64-x86_64-make \
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-pkg-config \
mingw-w64-x86_64-meson
```
For a 32 bits version, replace `x86_64` by `i686`:
```bash
# runtime dependencies
pacman -S mingw-w64-i686-SDL2 \
mingw-w64-i686-ffmpeg
# client build dependencies
pacman -S mingw-w64-i686-make \
mingw-w64-i686-gcc \
mingw-w64-i686-pkg-config \
mingw-w64-i686-meson
```
Java (>= 7) is not available in MSYS2, so if you plan to build the server,
install it manually and make it available from the `PATH`:
```bash
export PATH="$JAVA_HOME/bin:$PATH"
```
#### Mac OS
The application is available in [Homebrew]. Just install it:
@@ -66,18 +170,106 @@ The application is available in [Homebrew]. Just install it:
brew install scrcpy
```
You need `adb`, accessible from your `PATH`. If you don't have it yet:
Instead, you may want to build it manually. Install the packages:
```bash
brew cask install android-platform-tools
# runtime dependencies
brew install sdl2 ffmpeg
# client build dependencies
brew install pkg-config meson
```
You can also [build the app manually][BUILD].
Additionally, if you want to build the server, install Java 8 from Caskroom, and
make it avaliable from the `PATH`:
```bash
brew tap caskroom/versions
brew cask install java8
export JAVA_HOME="$(/usr/libexec/java_home --version 1.8)"
export PATH="$JAVA_HOME/bin:$PATH"
```
#### Docker
See [pierlon/scrcpy-docker](https://github.com/pierlon/scrcpy-docker).
### Common steps
Install the [Android SDK] (_Android Studio_), and set `ANDROID_HOME` to
its directory. For example:
[Android SDK]: https://developer.android.com/studio/index.html
```bash
export ANDROID_HOME=~/android/sdk
```
Clone the project:
```bash
git clone https://github.com/Genymobile/scrcpy
cd scrcpy
```
Then, build:
```bash
meson x --buildtype release --strip -Db_lto=true
cd x
ninja
```
You can test it from here:
```bash
ninja run
```
Or you can install it on the system:
```bash
sudo ninja install # without sudo on Windows
```
This installs two files:
- `/usr/local/bin/scrcpy`
- `/usr/local/share/scrcpy/scrcpy-server.jar`
Just remove them to "uninstall" the application.
#### Prebuilt server
Since the server binary, that will be pushed to the Android device, does not
depend on your system and architecture, you may want to use the prebuilt binary
instead:
- [`scrcpy-server-v1.3.jar`][direct-scrcpy-server].
_(SHA-256: 0f9a5a217f33f0ed7a1498ceb3c0cccf31c53533893aa952e674c1571d2740c1)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.3/scrcpy-server-v1.3.jar
In that case, the build does not require Java or the Android SDK.
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:
```bash
meson x --buildtype release --strip -Db_lto=true \
-Dprebuilt_server=/path/to/scrcpy-server.jar
cd x
ninja
sudo ninja install
```
## Run
Plug an Android device, and execute:
_At runtime, `adb` must be accessible from your `PATH`._
If everything is ok, just plug an Android device, and execute:
```bash
scrcpy
@@ -89,185 +281,50 @@ It accepts command-line arguments, listed by:
scrcpy --help
```
## Features
### Reduce size
Sometimes, it is useful to mirror an Android device at a lower definition to
increase performances.
To limit both width and height to some value (e.g. 1024):
For example, to decrease video bitrate to 2Mbps (default is 8Mbps):
```bash
scrcpy --max-size 1024
scrcpy -m 1024 # short version
scrcpy -b 2M
```
The other dimension is computed to that the device aspect-ratio is preserved.
That way, a device in 1920×1080 will be mirrored at 1024×576.
### Change bit-rate
The default bit-rate is 8Mbps. To change the video bitrate (e.g. to 2Mbps):
To limit the video dimensions (e.g. if the device is 2540×1440, but the host
screen is smaller, or cannot decode such a high definition):
```bash
scrcpy --bit-rate 2M
scrcpy -b 2M # short version
scrcpy -m 1024
```
### Crop
The device screen may be cropped to mirror only part of the screen.
This is useful for example to mirror only 1 eye of the Oculus Go:
The device screen may be cropped to mirror only part of the screen:
```bash
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
scrcpy -c 1224:1440:0:0 # short version
scrcpy -c 1224:1440:0:0 # 1224x1440 at offset (0,0)
```
If `--max-size` is also specified, resizing is applied after cropping.
### Wireless
_Scrcpy_ uses `adb` to communicate with the device, and `adb` can [connect] to a
device over TCP/IP:
1. Connect the device to the same Wi-Fi as your computer.
2. Get your device IP address (in Settings → About phone → Status).
3. Enable adb over TCP/IP on your device: `adb tcpip 5555`.
4. Unplug your device.
5. Connect to your device: `adb connect DEVICE_IP:5555` _(replace `DEVICE_IP`)_.
6. Run `scrcpy` as usual.
It may be useful to decrease the bit-rate and the definition:
```bash
scrcpy --bit-rate 2M --max-size 800
scrcpy -b2M -m800 # short version
```
[connect]: https://developer.android.com/studio/command-line/adb.html#wireless
### Record screen
It is possible to record the screen while mirroring:
```bash
scrcpy --record file.mp4
scrcpy -r file.mkv
```
To disable mirroring while recording:
```bash
scrcpy --no-display --record file.mp4
scrcpy -Nr file.mkv
# interrupt recording with Ctrl+C
# Ctrl+C does not terminate properly on Windows, so disconnect the device
```
"Skipped frames" are recorded, even if they are not displayed in real time (for
performance reasons). Frames are _timestamped_ on the device, so [packet delay
variation] does not impact the recorded file.
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
### Multi-devices
If several devices are listed in `adb devices`, you must specify the _serial_:
```bash
scrcpy --serial 0123456789abcdef
scrcpy -s 0123456789abcdef # short version
scrcpy -s 0123456789abcdef
```
You can start several instances of _scrcpy_ for several devices.
### Fullscreen
The app may be started directly in fullscreen:
To show physical touches while scrcpy is running:
```bash
scrcpy --fullscreen
scrcpy -f # short version
```
Fullscreen can then be toggled dynamically with `Ctrl`+`f`.
### Always on top
The window of app can always be above others by:
```bash
scrcpy --always-on-top
scrcpy -T # short version
```
### Show touches
For presentations, it may be useful to show physical touches (on the physical
device).
Android provides this feature in _Developers options_.
_Scrcpy_ provides an option to enable this feature on start and disable on exit:
```bash
scrcpy --show-touches
scrcpy -t
```
Note that it only shows _physical_ touches (with the finger on the device).
The app may be started directly in fullscreen:
### Install APK
To install an APK, drag & drop an APK file (ending with `.apk`) to the _scrcpy_
window.
There is no visual feedback, a log is printed to the console.
### Push file to device
To push a file to `/sdcard/` on the device, drag & drop a (non-APK) file to the
_scrcpy_ window.
There is no visual feedback, a log is printed to the console.
### Read-only
To disable controls (everything which can interact with the device: input keys,
mouse events, drag&drop files):
```bash
scrcpy --no-control
scrcpy -n
```
scrcpy -f
```
To run without installing:
### Forward audio
```bash
./run x [options]
```
Audio is not forwarded by _scrcpy_.
There is a limited solution using [AOA], implemented in the [`audio`] branch. If
you are interested, see [issue 14].
[AOA]: https://source.android.com/devices/accessories/aoa2
[`audio`]: https://github.com/Genymobile/scrcpy/commits/audio
[issue 14]: https://github.com/Genymobile/scrcpy/issues/14
(where `x` is your build directory).
## Shortcuts
@@ -281,32 +338,19 @@ you are interested, see [issue 14].
| click on `BACK` | `Ctrl`+`b` \| _Right-click²_ |
| click on `APP_SWITCH` | `Ctrl`+`s` |
| click on `MENU` | `Ctrl`+`m` |
| click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ (`Cmd`+`↑` on MacOS) |
| click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ (`Cmd`+`↓` on MacOS) |
| click on `VOLUME_UP` | `Ctrl`+`↑` _(up)_ |
| click on `VOLUME_DOWN` | `Ctrl`+`↓` _(down)_ |
| click on `POWER` | `Ctrl`+`p` |
| turn screen on | _Right-click²_ |
| expand notification panel | `Ctrl`+`n` |
| collapse notification panel | `Ctrl`+`Shift`+`n` |
| 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._
## Custom paths
To use a specific _adb_ binary, configure its path in the environment variable
`ADB`:
ADB=/path/to/adb scrcpy
To override the path of the `scrcpy-server.jar` file (it can be [useful] on
Windows), configure its path in `SCRCPY_SERVER_PATH`.
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
## Why _scrcpy_?
A colleague challenged me to find a name as unpronounceable as [gnirehtet].
@@ -317,13 +361,6 @@ A colleague challenged me to find a name as unpronounceable as [gnirehtet].
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
## How to build?
See [BUILD].
[BUILD]: BUILD.md
## Common issues
See the [FAQ](FAQ.md).

View File

@@ -8,17 +8,15 @@ src = [
'src/device.c',
'src/file_handler.c',
'src/fps_counter.c',
'src/frames.c',
'src/input_manager.c',
'src/lock_util.c',
'src/net.c',
'src/recorder.c',
'src/scrcpy.c',
'src/screen.c',
'src/server.c',
'src/str_util.c',
'src/tiny_xpm.c',
'src/stream.c',
'src/video_buffer.c',
]
if not get_option('crossbuild_windows')
@@ -87,7 +85,7 @@ conf = configuration_data()
conf.set('BUILD_DEBUG', get_option('buildtype') == 'debug')
# the version, updated on release
conf.set_quoted('SCRCPY_VERSION', meson.project_version())
conf.set_quoted('SCRCPY_VERSION', '1.3')
# the prefix used during configuration (meson --prefix=PREFIX)
conf.set_quoted('PREFIX', get_option('prefix'))
@@ -143,34 +141,18 @@ else
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, c_args: c_args, link_args: link_args)
### 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/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']],
]
foreach t : tests
exe = executable(t[0], t[1],
include_directories: src_dir,
dependencies: dependencies)
exe = executable(t[0], t[1], include_directories: src_dir, dependencies: dependencies)
test(t[0], exe)
endforeach

View File

@@ -1,33 +0,0 @@
#ifndef BUFFER_UTIL_H
#define BUFFER_UTIL_H
#include <stdbool.h>
#include <stdint.h>
static inline void
buffer_write16be(uint8_t *buf, uint16_t value) {
buf[0] = value >> 8;
buf[1] = value;
}
static inline void
buffer_write32be(uint8_t *buf, uint32_t value) {
buf[0] = value >> 24;
buf[1] = value >> 16;
buf[2] = value >> 8;
buf[3] = value;
}
static inline uint32_t
buffer_read32be(uint8_t *buf) {
return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
}
static inline
uint64_t buffer_read64be(uint8_t *buf) {
uint32_t msb = buffer_read32be(buf);
uint32_t lsb = buffer_read32be(&buf[4]);
return ((uint64_t) msb << 32) | lsb;
}
#endif

View File

@@ -1,18 +1,15 @@
#include "command.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "common.h"
#include "log.h"
#include "str_util.h"
static const char *adb_command;
static inline const char *
get_adb_command(void) {
static inline const char *get_adb_command() {
if (!adb_command) {
adb_command = getenv("ADB");
if (!adb_command)
@@ -21,52 +18,14 @@ get_adb_command(void) {
return adb_command;
}
// serialize argv to string "[arg1], [arg2], [arg3]"
static size_t
argv_to_string(const char *const *argv, char *buf, size_t bufsize) {
size_t idx = 0;
bool first = true;
while (*argv) {
const char *arg = *argv;
size_t len = strlen(arg);
// count space for "[], ...\0"
if (idx + len + 8 >= bufsize) {
// not enough space, truncate
assert(idx < bufsize - 4);
memcpy(&buf[idx], "...", 3);
idx += 3;
break;
}
if (first) {
first = false;
} else {
buf[idx++] = ',';
buf[idx++] = ' ';
}
buf[idx++] = '[';
memcpy(&buf[idx], arg, len);
idx += len;
buf[idx++] = ']';
argv++;
}
assert(idx < bufsize);
buf[idx] = '\0';
return idx;
}
static void
show_adb_err_msg(enum process_result err, const char *const argv[]) {
char buf[512];
static void show_adb_err_msg(enum process_result err) {
switch (err) {
case PROCESS_ERROR_GENERIC:
argv_to_string(argv, buf, sizeof(buf));
LOGE("Failed to execute: %s", buf);
LOGE("Failed to execute adb");
break;
case PROCESS_ERROR_MISSING_BINARY:
argv_to_string(argv, buf, sizeof(buf));
LOGE("Command not found: %s", buf);
LOGE("(make 'adb' accessible from your PATH or define its full"
"path in the ADB environment variable)");
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 */
@@ -74,8 +33,7 @@ show_adb_err_msg(enum process_result err, const char *const argv[]) {
}
}
process_t
adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
process_t adb_execute(const char *serial, const char *const adb_cmd[], int len) {
const char *cmd[len + 4];
int i;
process_t process;
@@ -92,15 +50,13 @@ adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
cmd[len + i] = NULL;
enum process_result r = cmd_execute(cmd[0], cmd, &process);
if (r != PROCESS_SUCCESS) {
show_adb_err_msg(r, cmd);
show_adb_err_msg(r);
return PROCESS_NONE;
}
return process;
}
process_t
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name) {
process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name) {
char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port);
@@ -109,17 +65,14 @@ adb_forward(const char *serial, uint16_t local_port,
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
process_t
adb_forward_remove(const char *serial, uint16_t local_port) {
process_t adb_forward_remove(const char *serial, uint16_t local_port) {
char local[4 + 5 + 1]; // tcp:PORT
sprintf(local, "tcp:%" PRIu16, local_port);
const char *const adb_cmd[] = {"forward", "--remove", local};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
process_t
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port) {
process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port) {
char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port);
@@ -128,67 +81,42 @@ adb_reverse(const char *serial, const char *device_socket_name,
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
process_t
adb_reverse_remove(const char *serial, const char *device_socket_name) {
process_t adb_reverse_remove(const char *serial, const char *device_socket_name) {
char remote[108 + 14 + 1]; // localabstract:NAME
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
const char *const adb_cmd[] = {"reverse", "--remove", remote};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
process_t
adb_push(const char *serial, const char *local, const char *remote) {
#ifdef __WINDOWS__
// Windows will parse the string, so the paths must be quoted
// (see sys/win/command.c)
local = strquote(local);
if (!local) {
return PROCESS_NONE;
}
remote = strquote(remote);
if (!remote) {
free((void *) local);
return PROCESS_NONE;
}
#endif
process_t adb_push(const char *serial, const char *local, const char *remote) {
const char *const adb_cmd[] = {"push", local, remote};
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__
free((void *) remote);
free((void *) local);
#endif
return proc;
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
process_t
adb_install(const char *serial, const char *local) {
process_t adb_install(const char *serial, const char *local) {
#ifdef __WINDOWS__
// Windows will parse the string, so the local name must be quoted
// (see sys/win/command.c)
local = strquote(local);
if (!local) {
return PROCESS_NONE;
}
// 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};
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__
free((void *) local);
#endif
return proc;
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
bool
process_check_success(process_t proc, const char *name) {
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));
}
SDL_bool process_check_success(process_t proc, const char *name) {
if (proc == PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name);
return false;
return SDL_FALSE;
}
exit_code_t exit_code;
if (!cmd_simple_wait(proc, &exit_code)) {
@@ -197,7 +125,7 @@ process_check_success(process_t proc, const char *name) {
} else {
LOGE("\"%s\" exited unexpectedly", name);
}
return false;
return SDL_FALSE;
}
return true;
return SDL_TRUE;
}

View File

@@ -1,36 +1,35 @@
#ifndef COMMAND_H
#define COMMAND_H
#include <stdbool.h>
#include <inttypes.h>
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_platform.h>
#ifdef _WIN32
// not needed here, but winsock2.h must never be included AFTER windows.h
# include <winsock2.h>
# include <windows.h>
# define PRIexitcode "lu"
// <https://stackoverflow.com/a/44383330/1987178>
#ifdef _WIN32
# define PRIexitcode "lu"
# ifdef _WIN64
# define PRIsizet PRIu64
# else
# define PRIsizet PRIu32
# endif
#else
# define PRIsizet "zu"
# define PRIexitcode "d"
#endif
#ifdef __WINDOWS__
# include <winsock2.h> // not needed here, but must never be included AFTER windows.h
# include <windows.h>
# define PROCESS_NONE NULL
typedef HANDLE process_t;
typedef DWORD exit_code_t;
#else
# include <sys/types.h>
# define PRIsizet "zu"
# define PRIexitcode "d"
# define PROCESS_NONE -1
typedef pid_t process_t;
typedef int exit_code_t;
#endif
# define NO_EXIT_CODE -1
enum process_result {
@@ -39,41 +38,21 @@ enum process_result {
PROCESS_ERROR_MISSING_BINARY,
};
enum process_result
cmd_execute(const char *path, const char *const argv[], process_t *process);
enum process_result cmd_execute(const char *path, const char *const argv[], process_t *process);
SDL_bool cmd_terminate(process_t pid);
SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code);
bool
cmd_terminate(process_t pid);
bool
cmd_simple_wait(process_t pid, exit_code_t *exit_code);
process_t
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
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_execute(const char *serial, const char *const adb_cmd[], int len);
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);
// convenience function to wait for a successful process execution
// automatically log process errors with the provided process name
bool
process_check_success(process_t process, const char *name);
SDL_bool process_check_success(process_t process, const char *name);
#endif

View File

@@ -1,26 +1,25 @@
#ifndef COMMON_H
#define COMMON_H
#include <stdint.h>
#include <SDL2/SDL_stdinc.h>
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
#define MIN(X,Y) (X) < (Y) ? (X) : (Y)
#define MAX(X,Y) (X) > (Y) ? (X) : (Y)
struct size {
uint16_t width;
uint16_t height;
Uint16 width;
Uint16 height;
};
struct point {
int32_t x;
int32_t y;
Uint16 x;
Uint16 y;
};
struct position {
// The video screen size may be different from the real device screen size,
// so store to which size the absolute position apply, to scale it
// accordingly.
// so store to which size the absolute position apply, to scale it accordingly.
struct size screen_size;
struct point point;
};

View File

@@ -1,51 +0,0 @@
#ifndef COMPAT_H
#define COMPAT_H
#include <libavformat/version.h>
#include <SDL2/SDL_version.h>
// In ffmpeg/doc/APIchanges:
// 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h
// Add AVStream.codecpar, deprecate AVStream.codec.
#if (LIBAVFORMAT_VERSION_MICRO >= 100 /* FFmpeg */ && \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)) \
|| (LIBAVFORMAT_VERSION_MICRO < 100 && /* Libav */ \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 5, 0))
# define SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
#endif
// In ffmpeg/doc/APIchanges:
// 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
// Deprecate use of av_register_input_format(), av_register_output_format(),
// av_register_all(), av_iformat_next(), av_oformat_next().
// Add av_demuxer_iterate(), and av_muxer_iterate().
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)
# define SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
#else
# define SCRCPY_LAVF_REQUIRES_REGISTER_ALL
#endif
// In ffmpeg/doc/APIchanges:
// 2016-04-21 - 7fc329e - lavc 57.37.100 - avcodec.h
// Add a new audio/video encoding and decoding API with decoupled input
// and output -- avcodec_send_packet(), avcodec_receive_frame(),
// avcodec_send_frame() and avcodec_receive_packet().
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100)
# define SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API
#endif
#if SDL_VERSION_ATLEAST(2, 0, 5)
// <https://wiki.libsdl.org/SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH>
# define SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
// <https://wiki.libsdl.org/SDL_GetDisplayUsableBounds>
# define SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
// <https://wiki.libsdl.org/SDL_WindowFlags>
# define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
#endif
#if SDL_VERSION_ATLEAST(2, 0, 8)
// <https://hg.libsdl.org/SDL/rev/dfde5d3f9781>
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
#endif
#endif

View File

@@ -1,49 +1,59 @@
#include "control_event.h"
#include <SDL2/SDL_stdinc.h>
#include <string.h>
#include "buffer_util.h"
#include "lock_util.h"
#include "log.h"
static void
write_position(uint8_t *buf, const struct position *position) {
buffer_write32be(&buf[0], position->point.x);
buffer_write32be(&buf[4], position->point.y);
buffer_write16be(&buf[8], position->screen_size.width);
buffer_write16be(&buf[10], position->screen_size.height);
static inline void write16(Uint8 *buf, Uint16 value) {
buf[0] = value >> 8;
buf[1] = value;
}
int
control_event_serialize(const struct control_event *event, unsigned char *buf) {
static inline void write32(Uint8 *buf, Uint32 value) {
buf[0] = value >> 24;
buf[1] = value >> 16;
buf[2] = value >> 8;
buf[3] = value;
}
static void write_position(Uint8 *buf, const struct position *position) {
write16(&buf[0], position->point.x);
write16(&buf[2], position->point.y);
write16(&buf[4], position->screen_size.width);
write16(&buf[6], position->screen_size.height);
}
int control_event_serialize(const struct control_event *event, unsigned char *buf) {
buf[0] = event->type;
switch (event->type) {
case CONTROL_EVENT_TYPE_KEYCODE:
buf[1] = event->keycode_event.action;
buffer_write32be(&buf[2], event->keycode_event.keycode);
buffer_write32be(&buf[6], event->keycode_event.metastate);
write32(&buf[2], event->keycode_event.keycode);
write32(&buf[6], event->keycode_event.metastate);
return 10;
case CONTROL_EVENT_TYPE_TEXT: {
// write length (2 bytes) + string (non nul-terminated)
// write length (1 byte) + date (non nul-terminated)
size_t len = strlen(event->text_event.text);
if (len > TEXT_MAX_LENGTH) {
// injecting a text takes time, so limit the text length
len = TEXT_MAX_LENGTH;
}
buffer_write16be(&buf[1], (uint16_t) len);
write16(&buf[1], (Uint16) len);
memcpy(&buf[3], event->text_event.text, len);
return 3 + len;
}
case CONTROL_EVENT_TYPE_MOUSE:
buf[1] = event->mouse_event.action;
buffer_write32be(&buf[2], event->mouse_event.buttons);
write32(&buf[2], event->mouse_event.buttons);
write_position(&buf[6], &event->mouse_event.position);
return 18;
return 14;
case CONTROL_EVENT_TYPE_SCROLL:
write_position(&buf[1], &event->scroll_event.position);
buffer_write32be(&buf[13], (uint32_t) event->scroll_event.hscroll);
buffer_write32be(&buf[17], (uint32_t) event->scroll_event.vscroll);
return 21;
write32(&buf[9], (Uint32) event->scroll_event.hscroll);
write32(&buf[13], (Uint32) event->scroll_event.vscroll);
return 17;
case CONTROL_EVENT_TYPE_COMMAND:
buf[1] = event->command_event.action;
return 2;
@@ -53,33 +63,28 @@ control_event_serialize(const struct control_event *event, unsigned char *buf) {
}
}
void
control_event_destroy(struct control_event *event) {
void control_event_destroy(struct control_event *event) {
if (event->type == CONTROL_EVENT_TYPE_TEXT) {
SDL_free(event->text_event.text);
}
}
bool
control_event_queue_is_empty(const struct control_event_queue *queue) {
SDL_bool control_event_queue_is_empty(const struct control_event_queue *queue) {
return queue->head == queue->tail;
}
bool
control_event_queue_is_full(const struct control_event_queue *queue) {
SDL_bool control_event_queue_is_full(const struct control_event_queue *queue) {
return (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE == queue->tail;
}
bool
control_event_queue_init(struct control_event_queue *queue) {
SDL_bool control_event_queue_init(struct control_event_queue *queue) {
queue->head = 0;
queue->tail = 0;
// the current implementation may not fail
return true;
return SDL_TRUE;
}
void
control_event_queue_destroy(struct control_event_queue *queue) {
void control_event_queue_destroy(struct control_event_queue *queue) {
int i = queue->tail;
while (i != queue->head) {
control_event_destroy(&queue->data[i]);
@@ -87,24 +92,20 @@ control_event_queue_destroy(struct control_event_queue *queue) {
}
}
bool
control_event_queue_push(struct control_event_queue *queue,
const struct control_event *event) {
SDL_bool control_event_queue_push(struct control_event_queue *queue, const struct control_event *event) {
if (control_event_queue_is_full(queue)) {
return false;
return SDL_FALSE;
}
queue->data[queue->head] = *event;
queue->head = (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE;
return true;
return SDL_TRUE;
}
bool
control_event_queue_take(struct control_event_queue *queue,
struct control_event *event) {
SDL_bool control_event_queue_take(struct control_event_queue *queue, struct control_event *event) {
if (control_event_queue_is_empty(queue)) {
return false;
return SDL_FALSE;
}
*event = queue->data[queue->tail];
queue->tail = (queue->tail + 1) % CONTROL_EVENT_QUEUE_SIZE;
return true;
return SDL_TRUE;
}

View File

@@ -1,9 +1,8 @@
#ifndef CONTROLEVENT_H
#define CONTROLEVENT_H
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include "android/input.h"
#include "android/keycodes.h"
@@ -21,11 +20,7 @@ enum control_event_type {
CONTROL_EVENT_TYPE_COMMAND,
};
enum control_event_command {
CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON,
CONTROL_EVENT_COMMAND_EXPAND_NOTIFICATION_PANEL,
CONTROL_EVENT_COMMAND_COLLAPSE_NOTIFICATION_PANEL,
};
#define CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON 0
struct control_event {
enum control_event_type type;
@@ -45,11 +40,11 @@ struct control_event {
} mouse_event;
struct {
struct position position;
int32_t hscroll;
int32_t vscroll;
Sint32 hscroll;
Sint32 vscroll;
} scroll_event;
struct {
enum control_event_command action;
int action;
} command_event;
};
};
@@ -61,31 +56,18 @@ struct control_event_queue {
};
// buf size must be at least SERIALIZED_EVENT_MAX_SIZE
int
control_event_serialize(const struct control_event *event, unsigned char *buf);
int control_event_serialize(const struct control_event *event, unsigned char *buf);
bool
control_event_queue_init(struct control_event_queue *queue);
SDL_bool control_event_queue_init(struct control_event_queue *queue);
void control_event_queue_destroy(struct control_event_queue *queue);
void
control_event_queue_destroy(struct control_event_queue *queue);
bool
control_event_queue_is_empty(const struct control_event_queue *queue);
bool
control_event_queue_is_full(const struct control_event_queue *queue);
SDL_bool control_event_queue_is_empty(const struct control_event_queue *queue);
SDL_bool control_event_queue_is_full(const struct control_event_queue *queue);
// event is copied, the queue does not use the event after the function returns
bool
control_event_queue_push(struct control_event_queue *queue,
const struct control_event *event);
SDL_bool control_event_queue_push(struct control_event_queue *queue, const struct control_event *event);
SDL_bool control_event_queue_take(struct control_event_queue *queue, struct control_event *event);
bool
control_event_queue_take(struct control_event_queue *queue,
struct control_event *event);
void
control_event_destroy(struct control_event *event);
void control_event_destroy(struct control_event *event);
#endif

View File

@@ -1,45 +1,40 @@
#include "controller.h"
#include <SDL2/SDL_assert.h>
#include "config.h"
#include "lock_util.h"
#include "log.h"
bool
controller_init(struct controller *controller, socket_t video_socket) {
SDL_bool controller_init(struct controller *controller, socket_t video_socket) {
if (!control_event_queue_init(&controller->queue)) {
return false;
return SDL_FALSE;
}
if (!(controller->mutex = SDL_CreateMutex())) {
return false;
return SDL_FALSE;
}
if (!(controller->event_cond = SDL_CreateCond())) {
SDL_DestroyMutex(controller->mutex);
return false;
return SDL_FALSE;
}
controller->video_socket = video_socket;
controller->stopped = false;
controller->stopped = SDL_FALSE;
return true;
return SDL_TRUE;
}
void
controller_destroy(struct controller *controller) {
void controller_destroy(struct controller *controller) {
SDL_DestroyCond(controller->event_cond);
SDL_DestroyMutex(controller->mutex);
control_event_queue_destroy(&controller->queue);
}
bool
controller_push_event(struct controller *controller,
const struct control_event *event) {
bool res;
SDL_bool controller_push_event(struct controller *controller, const struct control_event *event) {
SDL_bool res;
mutex_lock(controller->mutex);
bool was_empty = control_event_queue_is_empty(&controller->queue);
SDL_bool was_empty = control_event_queue_is_empty(&controller->queue);
res = control_event_queue_push(&controller->queue, event);
if (was_empty) {
cond_signal(controller->event_cond);
@@ -48,26 +43,22 @@ controller_push_event(struct controller *controller,
return res;
}
static bool
process_event(struct controller *controller,
const struct control_event *event) {
static SDL_bool process_event(struct controller *controller, const struct control_event *event) {
unsigned char serialized_event[SERIALIZED_EVENT_MAX_SIZE];
int length = control_event_serialize(event, serialized_event);
if (!length) {
return false;
return SDL_FALSE;
}
int w = net_send_all(controller->video_socket, serialized_event, length);
return w == length;
}
static int
run_controller(void *data) {
static int run_controller(void *data) {
struct controller *controller = data;
for (;;) {
mutex_lock(controller->mutex);
while (!controller->stopped
&& control_event_queue_is_empty(&controller->queue)) {
while (!controller->stopped && control_event_queue_is_empty(&controller->queue)) {
cond_wait(controller->event_cond, controller->mutex);
}
if (controller->stopped) {
@@ -76,12 +67,11 @@ run_controller(void *data) {
break;
}
struct control_event event;
bool non_empty = control_event_queue_take(&controller->queue,
&event);
SDL_bool non_empty = control_event_queue_take(&controller->queue, &event);
SDL_assert(non_empty);
mutex_unlock(controller->mutex);
bool ok = process_event(controller, &event);
SDL_bool ok = process_event(controller, &event);
control_event_destroy(&event);
if (!ok) {
LOGD("Cannot write event to socket");
@@ -91,29 +81,25 @@ run_controller(void *data) {
return 0;
}
bool
controller_start(struct controller *controller) {
SDL_bool controller_start(struct controller *controller) {
LOGD("Starting controller thread");
controller->thread = SDL_CreateThread(run_controller, "controller",
controller);
controller->thread = SDL_CreateThread(run_controller, "controller", controller);
if (!controller->thread) {
LOGC("Could not start controller thread");
return false;
return SDL_FALSE;
}
return true;
return SDL_TRUE;
}
void
controller_stop(struct controller *controller) {
void controller_stop(struct controller *controller) {
mutex_lock(controller->mutex);
controller->stopped = true;
controller->stopped = SDL_TRUE;
cond_signal(controller->event_cond);
mutex_unlock(controller->mutex);
}
void
controller_join(struct controller *controller) {
void controller_join(struct controller *controller) {
SDL_WaitThread(controller->thread, NULL);
}

View File

@@ -3,8 +3,8 @@
#include "control_event.h"
#include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_thread.h>
#include "net.h"
@@ -14,28 +14,18 @@ struct controller {
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *event_cond;
bool stopped;
SDL_bool stopped;
struct control_event_queue queue;
};
bool
controller_init(struct controller *controller, socket_t video_socket);
SDL_bool controller_init(struct controller *controller, socket_t video_socket);
void controller_destroy(struct controller *controller);
void
controller_destroy(struct controller *controller);
bool
controller_start(struct controller *controller);
void
controller_stop(struct controller *controller);
void
controller_join(struct controller *controller);
SDL_bool controller_start(struct controller *controller);
void controller_stop(struct controller *controller);
void controller_join(struct controller *controller);
// expose simple API to hide control_event_queue
bool
controller_push_event(struct controller *controller,
const struct control_event *event);
SDL_bool controller_push_event(struct controller *controller, const struct control_event *event);
#endif

View File

@@ -1,10 +1,9 @@
#include "convert.h"
#define MAP(FROM, TO) case FROM: *to = TO; return true
#define FAIL default: return false
#define MAP(FROM, TO) case FROM: *to = TO; return SDL_TRUE
#define FAIL default: return SDL_FALSE
static bool
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
static SDL_bool convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
switch (from) {
MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN);
MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP);
@@ -12,8 +11,7 @@ convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
}
}
static enum android_metastate
autocomplete_metastate(enum android_metastate metastate) {
static enum android_metastate autocomplete_metastate(enum android_metastate metastate) {
// fill dependant flags
if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
metastate |= AMETA_SHIFT_ON;
@@ -32,8 +30,7 @@ autocomplete_metastate(enum android_metastate metastate) {
}
static enum android_metastate
convert_meta_state(SDL_Keymod mod) {
static enum android_metastate convert_meta_state(SDL_Keymod mod) {
enum android_metastate metastate = 0;
if (mod & KMOD_LSHIFT) {
metastate |= AMETA_SHIFT_LEFT_ON;
@@ -73,62 +70,27 @@ convert_meta_state(SDL_Keymod mod) {
return autocomplete_metastate(metastate);
}
static bool
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) {
static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to) {
switch (from) {
MAP(SDLK_RETURN, AKEYCODE_ENTER);
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE);
MAP(SDLK_BACKSPACE, AKEYCODE_DEL);
MAP(SDLK_TAB, AKEYCODE_TAB);
MAP(SDLK_HOME, AKEYCODE_HOME);
MAP(SDLK_PAGEUP, AKEYCODE_PAGE_UP);
MAP(SDLK_DELETE, AKEYCODE_FORWARD_DEL);
MAP(SDLK_HOME, AKEYCODE_MOVE_HOME);
MAP(SDLK_END, AKEYCODE_MOVE_END);
MAP(SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN);
MAP(SDLK_RIGHT, AKEYCODE_DPAD_RIGHT);
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
}
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
return false;
}
// if ALT and META are not pressed, also handle letters and space
switch (from) {
MAP(SDLK_a, AKEYCODE_A);
MAP(SDLK_b, AKEYCODE_B);
MAP(SDLK_c, AKEYCODE_C);
MAP(SDLK_d, AKEYCODE_D);
MAP(SDLK_e, AKEYCODE_E);
MAP(SDLK_f, AKEYCODE_F);
MAP(SDLK_g, AKEYCODE_G);
MAP(SDLK_h, AKEYCODE_H);
MAP(SDLK_i, AKEYCODE_I);
MAP(SDLK_j, AKEYCODE_J);
MAP(SDLK_k, AKEYCODE_K);
MAP(SDLK_l, AKEYCODE_L);
MAP(SDLK_m, AKEYCODE_M);
MAP(SDLK_n, AKEYCODE_N);
MAP(SDLK_o, AKEYCODE_O);
MAP(SDLK_p, AKEYCODE_P);
MAP(SDLK_q, AKEYCODE_Q);
MAP(SDLK_r, AKEYCODE_R);
MAP(SDLK_s, AKEYCODE_S);
MAP(SDLK_t, AKEYCODE_T);
MAP(SDLK_u, AKEYCODE_U);
MAP(SDLK_v, AKEYCODE_V);
MAP(SDLK_w, AKEYCODE_W);
MAP(SDLK_x, AKEYCODE_X);
MAP(SDLK_y, AKEYCODE_Y);
MAP(SDLK_z, AKEYCODE_Z);
MAP(SDLK_SPACE, AKEYCODE_SPACE);
FAIL;
}
}
static bool
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
static SDL_bool convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) {
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
@@ -136,8 +98,7 @@ convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
}
}
static enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state) {
static enum android_motionevent_buttons convert_mouse_buttons(Uint32 state) {
enum android_motionevent_buttons buttons = 0;
if (state & SDL_BUTTON_LMASK) {
buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
@@ -157,33 +118,30 @@ convert_mouse_buttons(uint32_t state) {
return buttons;
}
bool
input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
struct control_event *to) {
SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_KEYCODE;
if (!convert_keycode_action(from->type, &to->keycode_event.action)) {
return false;
return SDL_FALSE;
}
uint16_t mod = from->keysym.mod;
if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode, mod)) {
return false;
if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode)) {
return SDL_FALSE;
}
to->keycode_event.metastate = convert_meta_state(mod);
to->keycode_event.metastate = convert_meta_state(from->keysym.mod);
return true;
return SDL_TRUE;
}
bool
mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
struct size screen_size,
struct control_event *to) {
SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
struct size screen_size,
struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_MOUSE;
if (!convert_mouse_action(from->type, &to->mouse_event.action)) {
return false;
return SDL_FALSE;
}
to->mouse_event.buttons = convert_mouse_buttons(SDL_BUTTON(from->button));
@@ -191,13 +149,12 @@ mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
to->mouse_event.position.point.x = from->x;
to->mouse_event.position.point.y = from->y;
return true;
return SDL_TRUE;
}
bool
mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
struct size screen_size,
struct control_event *to) {
SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
struct size screen_size,
struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_MOUSE;
to->mouse_event.action = AMOTION_EVENT_ACTION_MOVE;
to->mouse_event.buttons = convert_mouse_buttons(from->state);
@@ -205,13 +162,12 @@ mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
to->mouse_event.position.point.x = from->x;
to->mouse_event.position.point.y = from->y;
return true;
return SDL_TRUE;
}
bool
mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
struct position position,
struct control_event *to) {
SDL_bool mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
struct position position,
struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_SCROLL;
to->scroll_event.position = position;
@@ -223,5 +179,5 @@ mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
to->scroll_event.hscroll = -mul * from->x;
to->scroll_event.vscroll = mul * from->y;
return true;
return SDL_TRUE;
}

View File

@@ -1,9 +1,8 @@
#ifndef CONVERT_H
#define CONVERT_H
#include <stdbool.h>
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_events.h>
#include "control_event.h"
struct complete_mouse_motion_event {
@@ -16,26 +15,21 @@ struct complete_mouse_wheel_event {
struct point position;
};
bool
input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
struct control_event *to);
SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
struct control_event *to);
SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
struct size screen_size,
struct control_event *to);
bool
mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
struct size screen_size,
struct control_event *to);
// the video size may be different from the real device size, so we need the
// size to which the absolute position apply, to scale it accordingly
bool
mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
struct size screen_size,
struct control_event *to);
// the video size may be different from the real device size, so we need the size
// to which the absolute position apply, to scale it accordingly
SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
struct size screen_size,
struct control_event *to);
// on Android, a scroll event requires the current mouse position
bool
mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
struct position position,
struct control_event *to);
SDL_bool mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
struct position position,
struct control_event *to);
#endif

View File

@@ -1,29 +1,28 @@
#include "decoder.h"
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include <unistd.h>
#include "compat.h"
#include "config.h"
#include "buffer_util.h"
#include "events.h"
#include "frames.h"
#include "lock_util.h"
#include "log.h"
#include "recorder.h"
#include "video_buffer.h"
#define BUFSIZE 0x10000
static int read_packet(void *opaque, uint8_t *buf, int buf_size) {
struct decoder *decoder = opaque;
return net_recv(decoder->video_socket, buf, buf_size);
}
// set the decoded frame as ready for rendering, and notify
static void
push_frame(struct decoder *decoder) {
bool previous_frame_skipped;
video_buffer_offer_decoded_frame(decoder->video_buffer,
&previous_frame_skipped);
if (previous_frame_skipped) {
static void push_frame(struct decoder *decoder) {
SDL_bool previous_frame_consumed = frames_offer_decoded_frame(decoder->frames);
if (!previous_frame_consumed) {
// the previous EVENT_NEW_FRAME will consume this frame
return;
}
@@ -33,71 +32,143 @@ push_frame(struct decoder *decoder) {
SDL_PushEvent(&new_frame_event);
}
void
decoder_init(struct decoder *decoder, struct video_buffer *vb) {
decoder->video_buffer = vb;
static void notify_stopped(void) {
SDL_Event stop_event;
stop_event.type = EVENT_DECODER_STOPPED;
SDL_PushEvent(&stop_event);
}
bool
decoder_open(struct decoder *decoder, const AVCodec *codec) {
decoder->codec_ctx = avcodec_alloc_context3(codec);
if (!decoder->codec_ctx) {
static int run_decoder(void *data) {
struct decoder *decoder = data;
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec) {
LOGE("H.264 decoder not found");
goto run_end;
}
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
LOGC("Could not allocate decoder context");
return false;
goto run_end;
}
if (avcodec_open2(decoder->codec_ctx, codec, NULL) < 0) {
LOGE("Could not open codec");
avcodec_free_context(&decoder->codec_ctx);
return false;
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
LOGE("Could not open H.264 codec");
goto run_finally_free_codec_ctx;
}
return true;
}
AVFormatContext *format_ctx = avformat_alloc_context();
if (!format_ctx) {
LOGC("Could not allocate format context");
goto run_finally_close_codec;
}
void
decoder_close(struct decoder *decoder) {
avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx);
}
unsigned char *buffer = av_malloc(BUFSIZE);
if (!buffer) {
LOGC("Could not allocate buffer");
goto run_finally_free_format_ctx;
}
bool
decoder_push(struct decoder *decoder, const AVPacket *packet) {
AVIOContext *avio_ctx = avio_alloc_context(buffer, BUFSIZE, 0, decoder, read_packet, NULL, NULL);
if (!avio_ctx) {
LOGC("Could not allocate avio context");
// avformat_open_input takes ownership of 'buffer'
// so only free the buffer before avformat_open_input()
av_free(buffer);
goto run_finally_free_format_ctx;
}
format_ctx->pb = avio_ctx;
if (avformat_open_input(&format_ctx, NULL, NULL, NULL) < 0) {
LOGE("Could not open video stream");
goto run_finally_free_avio_ctx;
}
AVPacket packet;
av_init_packet(&packet);
packet.data = NULL;
packet.size = 0;
while (!av_read_frame(format_ctx, &packet)) {
// the new decoding/encoding API has been introduced by:
// <http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=7fc329e2dd6226dfecaa4a1d7adf353bf2773726>
#ifdef SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API
int ret;
if ((ret = avcodec_send_packet(decoder->codec_ctx, packet)) < 0) {
LOGE("Could not send video packet: %d", ret);
return false;
}
ret = avcodec_receive_frame(decoder->codec_ctx,
decoder->video_buffer->decoding_frame);
if (!ret) {
// a frame was received
push_frame(decoder);
} else if (ret != AVERROR(EAGAIN)) {
LOGE("Could not receive video frame: %d", ret);
return false;
}
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 0)
int ret;
if ((ret = avcodec_send_packet(codec_ctx, &packet)) < 0) {
LOGE("Could not send video packet: %d", ret);
goto run_quit;
}
ret = avcodec_receive_frame(codec_ctx, decoder->frames->decoding_frame);
if (!ret) {
// a frame was received
push_frame(decoder);
} else if (ret != AVERROR(EAGAIN)) {
LOGE("Could not receive video frame: %d", ret);
av_packet_unref(&packet);
goto run_quit;
}
#else
int got_picture;
int len = avcodec_decode_video2(decoder->codec_ctx,
decoder->video_buffer->decoding_frame,
&got_picture,
packet);
if (len < 0) {
LOGE("Could not decode video packet: %d", len);
return false;
}
if (got_picture) {
push_frame(decoder);
}
while (packet.size > 0) {
int got_picture;
int len = avcodec_decode_video2(codec_ctx, decoder->frames->decoding_frame, &got_picture, &packet);
if (len < 0) {
LOGE("Could not decode video packet: %d", len);
goto run_quit;
}
if (got_picture) {
push_frame(decoder);
}
packet.size -= len;
packet.data += len;
}
#endif
return true;
av_packet_unref(&packet);
if (avio_ctx->eof_reached) {
break;
}
}
LOGD("End of frames");
run_quit:
avformat_close_input(&format_ctx);
run_finally_free_avio_ctx:
av_freep(&avio_ctx);
run_finally_free_format_ctx:
avformat_free_context(format_ctx);
run_finally_close_codec:
avcodec_close(codec_ctx);
run_finally_free_codec_ctx:
avcodec_free_context(&codec_ctx);
notify_stopped();
run_end:
return 0;
}
void
decoder_interrupt(struct decoder *decoder) {
video_buffer_interrupt(decoder->video_buffer);
void decoder_init(struct decoder *decoder, struct frames *frames, socket_t video_socket) {
decoder->frames = frames;
decoder->video_socket = video_socket;
}
SDL_bool decoder_start(struct decoder *decoder) {
LOGD("Starting decoder thread");
decoder->thread = SDL_CreateThread(run_decoder, "video_decoder", decoder);
if (!decoder->thread) {
LOGC("Could not start decoder thread");
return SDL_FALSE;
}
return SDL_TRUE;
}
void decoder_stop(struct decoder *decoder) {
frames_stop(decoder->frames);
}
void decoder_join(struct decoder *decoder) {
SDL_WaitThread(decoder->thread, NULL);
}

View File

@@ -1,29 +1,23 @@
#ifndef DECODER_H
#define DECODER_H
#include <stdbool.h>
#include <libavformat/avformat.h>
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_thread.h>
struct video_buffer;
#include "net.h"
struct frames;
struct decoder {
struct video_buffer *video_buffer;
AVCodecContext *codec_ctx;
struct frames *frames;
socket_t video_socket;
SDL_Thread *thread;
SDL_mutex *mutex;
};
void
decoder_init(struct decoder *decoder, struct video_buffer *vb);
bool
decoder_open(struct decoder *decoder, const AVCodec *codec);
void
decoder_close(struct decoder *decoder);
bool
decoder_push(struct decoder *decoder, const AVPacket *packet);
void
decoder_interrupt(struct decoder *decoder);
void decoder_init(struct decoder *decoder, struct frames *frames, socket_t video_socket);
SDL_bool decoder_start(struct decoder *decoder);
void decoder_stop(struct decoder *decoder);
void decoder_join(struct decoder *decoder);
#endif

View File

@@ -1,22 +1,18 @@
#include "device.h"
#include "log.h"
bool
device_read_info(socket_t device_socket, char *device_name, struct size *size) {
SDL_bool device_read_info(socket_t device_socket, char *device_name, struct size *size) {
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
int r = net_recv_all(device_socket, buf, sizeof(buf));
if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
LOGE("Could not retrieve device information");
return false;
return SDL_FALSE;
}
// in case the client sends garbage
buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
// strcpy is safe here, since name contains at least
// DEVICE_NAME_FIELD_LENGTH bytes and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; // in case the client sends garbage
// strcpy is safe here, since name contains at least DEVICE_NAME_FIELD_LENGTH bytes
// and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
strcpy(device_name, (char *) buf);
size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8)
| buf[DEVICE_NAME_FIELD_LENGTH + 1];
size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8)
| buf[DEVICE_NAME_FIELD_LENGTH + 3];
return true;
size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 1];
size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 3];
return SDL_TRUE;
}

View File

@@ -1,7 +1,7 @@
#ifndef DEVICE_H
#define DEVICE_H
#include <stdbool.h>
#include <SDL2/SDL_stdinc.h>
#include "common.h"
#include "net.h"
@@ -10,7 +10,6 @@
#define DEVICE_SDCARD_PATH "/sdcard/"
// name must be at least DEVICE_NAME_FIELD_LENGTH bytes
bool
device_read_info(socket_t device_socket, char *name, struct size *frame_size);
SDL_bool device_read_info(socket_t device_socket, char *name, struct size *frame_size);
#endif

View File

@@ -1,3 +1,3 @@
#define EVENT_NEW_SESSION SDL_USEREVENT
#define EVENT_NEW_FRAME (SDL_USEREVENT + 1)
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 2)
#define EVENT_DECODER_STOPPED (SDL_USEREVENT + 2)

View File

@@ -10,11 +10,10 @@
struct request {
file_handler_action_t action;
char *file;
const char *file;
};
static struct request *
request_new(file_handler_action_t action, 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;
@@ -24,34 +23,29 @@ request_new(file_handler_action_t action, char *file) {
return req;
}
static void
request_free(struct request *req) {
static void request_free(struct request *req) {
if (!req) {
return;
}
SDL_free(req->file);
SDL_free(req);
SDL_free((void *) req->file);
SDL_free((void *) req);
}
static bool
request_queue_is_empty(const struct request_queue *queue) {
static SDL_bool request_queue_is_empty(const struct request_queue *queue) {
return queue->head == queue->tail;
}
static bool
request_queue_is_full(const struct request_queue *queue) {
static SDL_bool request_queue_is_full(const struct request_queue *queue) {
return (queue->head + 1) % REQUEST_QUEUE_SIZE == queue->tail;
}
static bool
request_queue_init(struct request_queue *queue) {
static SDL_bool request_queue_init(struct request_queue *queue) {
queue->head = 0;
queue->tail = 0;
return true;
return SDL_TRUE;
}
static void
request_queue_destroy(struct request_queue *queue) {
static void request_queue_destroy(struct request_queue *queue) {
int i = queue->tail;
while (i != queue->head) {
request_free(queue->reqs[i]);
@@ -59,41 +53,38 @@ request_queue_destroy(struct request_queue *queue) {
}
}
static bool
request_queue_push(struct request_queue *queue, struct request *req) {
static SDL_bool request_queue_push(struct request_queue *queue, struct request *req) {
if (request_queue_is_full(queue)) {
return false;
return SDL_FALSE;
}
queue->reqs[queue->head] = req;
queue->head = (queue->head + 1) % REQUEST_QUEUE_SIZE;
return true;
return SDL_TRUE;
}
static bool
request_queue_take(struct request_queue *queue, struct request **req) {
static SDL_bool request_queue_take(struct request_queue *queue, struct request **req) {
if (request_queue_is_empty(queue)) {
return false;
return SDL_FALSE;
}
// transfer ownership
*req = queue->reqs[queue->tail];
queue->tail = (queue->tail + 1) % REQUEST_QUEUE_SIZE;
return true;
return SDL_TRUE;
}
bool
file_handler_init(struct file_handler *file_handler, const char *serial) {
SDL_bool file_handler_init(struct file_handler *file_handler, const char *serial) {
if (!request_queue_init(&file_handler->queue)) {
return false;
return SDL_FALSE;
}
if (!(file_handler->mutex = SDL_CreateMutex())) {
return false;
return SDL_FALSE;
}
if (!(file_handler->event_cond = SDL_CreateCond())) {
SDL_DestroyMutex(file_handler->mutex);
return false;
return SDL_FALSE;
}
if (serial) {
@@ -101,63 +92,58 @@ file_handler_init(struct file_handler *file_handler, const char *serial) {
if (!file_handler->serial) {
LOGW("Cannot strdup serial");
SDL_DestroyMutex(file_handler->mutex);
return false;
return SDL_FALSE;
}
} else {
file_handler->serial = NULL;
}
// lazy initialization
file_handler->initialized = false;
file_handler->initialized = SDL_FALSE;
file_handler->stopped = false;
file_handler->stopped = SDL_FALSE;
file_handler->current_process = PROCESS_NONE;
return true;
return SDL_TRUE;
}
void
file_handler_destroy(struct file_handler *file_handler) {
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(file_handler->serial);
SDL_free((void *) file_handler->serial);
}
static process_t
install_apk(const char *serial, const char *file) {
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) {
static process_t push_file(const char *serial, const char *file) {
return adb_push(serial, file, DEVICE_SDCARD_PATH);
}
bool
file_handler_request(struct file_handler *file_handler,
file_handler_action_t action,
char *file) {
bool res;
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 false;
return SDL_FALSE;
}
file_handler->initialized = true;
file_handler->initialized = SDL_TRUE;
}
LOGI("Request to %s %s", action == ACTION_INSTALL_APK ? "install" : "push",
file);
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 false;
return SDL_FALSE;
}
mutex_lock(file_handler->mutex);
bool was_empty = request_queue_is_empty(&file_handler->queue);
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);
@@ -166,15 +152,13 @@ file_handler_request(struct file_handler *file_handler,
return res;
}
static int
run_file_handler(void *data) {
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)) {
while (!file_handler->stopped && request_queue_is_empty(&file_handler->queue)) {
cond_wait(file_handler->event_cond, file_handler->mutex);
}
if (file_handler->stopped) {
@@ -183,7 +167,7 @@ run_file_handler(void *data) {
break;
}
struct request *req;
bool non_empty = request_queue_take(&file_handler->queue, &req);
SDL_bool non_empty = request_queue_take(&file_handler->queue, &req);
SDL_assert(non_empty);
process_t process;
@@ -216,24 +200,21 @@ run_file_handler(void *data) {
return 0;
}
bool
file_handler_start(struct file_handler *file_handler) {
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);
file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler", file_handler);
if (!file_handler->thread) {
LOGC("Could not start file_handler thread");
return false;
return SDL_FALSE;
}
return true;
return SDL_TRUE;
}
void
file_handler_stop(struct file_handler *file_handler) {
void file_handler_stop(struct file_handler *file_handler) {
mutex_lock(file_handler->mutex);
file_handler->stopped = true;
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)) {
@@ -245,7 +226,6 @@ file_handler_stop(struct file_handler *file_handler) {
mutex_unlock(file_handler->mutex);
}
void
file_handler_join(struct file_handler *file_handler) {
void file_handler_join(struct file_handler *file_handler) {
SDL_WaitThread(file_handler->thread, NULL);
}

View File

@@ -1,10 +1,9 @@
#ifndef FILE_HANDLER_H
#define FILE_HANDLER_H
#include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_thread.h>
#include "command.h"
#define REQUEST_QUEUE_SIZE 16
@@ -21,35 +20,25 @@ struct request_queue {
};
struct file_handler {
char *serial;
const char *serial;
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *event_cond;
bool stopped;
bool initialized;
SDL_bool stopped;
SDL_bool initialized;
process_t current_process;
struct request_queue queue;
};
bool
file_handler_init(struct file_handler *file_handler, const char *serial);
SDL_bool file_handler_init(struct file_handler *file_handler, const char *serial);
void file_handler_destroy(struct file_handler *file_handler);
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);
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);
// take ownership of file, and will SDL_free() it
bool
file_handler_request(struct file_handler *file_handler,
file_handler_action_t action,
char *file);
SDL_bool file_handler_request(struct file_handler *file_handler,
file_handler_action_t action,
const char *file);
#endif

View File

@@ -4,16 +4,14 @@
#include "log.h"
void
fps_counter_init(struct fps_counter *counter) {
counter->started = false;
void fps_counter_init(struct fps_counter *counter) {
counter->started = SDL_FALSE;
// no need to initialize the other fields, they are meaningful only when
// started is true
}
void
fps_counter_start(struct fps_counter *counter) {
counter->started = true;
void fps_counter_start(struct fps_counter *counter) {
counter->started = SDL_TRUE;
counter->slice_start = SDL_GetTicks();
counter->nr_rendered = 0;
#ifdef SKIP_FRAMES
@@ -21,17 +19,14 @@ fps_counter_start(struct fps_counter *counter) {
#endif
}
void
fps_counter_stop(struct fps_counter *counter) {
counter->started = false;
void fps_counter_stop(struct fps_counter *counter) {
counter->started = SDL_FALSE;
}
static void
display_fps(struct fps_counter *counter) {
static void display_fps(struct fps_counter *counter) {
#ifdef SKIP_FRAMES
if (counter->nr_skipped) {
LOGI("%d fps (+%d frames skipped)", counter->nr_rendered,
counter->nr_skipped);
LOGI("%d fps (+%d frames skipped)", counter->nr_rendered, counter->nr_skipped);
} else {
#endif
LOGI("%d fps", counter->nr_rendered);
@@ -40,13 +35,12 @@ display_fps(struct fps_counter *counter) {
#endif
}
static void
check_expired(struct fps_counter *counter) {
uint32_t now = SDL_GetTicks();
static void check_expired(struct fps_counter *counter) {
Uint32 now = SDL_GetTicks();
if (now - counter->slice_start >= 1000) {
display_fps(counter);
// add a multiple of one second
uint32_t elapsed_slices = (now - counter->slice_start) / 1000;
Uint32 elapsed_slices = (now - counter->slice_start) / 1000;
counter->slice_start += 1000 * elapsed_slices;
counter->nr_rendered = 0;
#ifdef SKIP_FRAMES
@@ -55,15 +49,13 @@ check_expired(struct fps_counter *counter) {
}
}
void
fps_counter_add_rendered_frame(struct fps_counter *counter) {
void fps_counter_add_rendered_frame(struct fps_counter *counter) {
check_expired(counter);
++counter->nr_rendered;
}
#ifdef SKIP_FRAMES
void
fps_counter_add_skipped_frame(struct fps_counter *counter) {
void fps_counter_add_skipped_frame(struct fps_counter *counter) {
check_expired(counter);
++counter->nr_skipped;
}

View File

@@ -1,35 +1,26 @@
#ifndef FPSCOUNTER_H
#define FPSCOUNTER_H
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_stdinc.h>
#include "config.h"
struct fps_counter {
bool started;
uint32_t slice_start; // initialized by SDL_GetTicks()
SDL_bool started;
Uint32 slice_start; // initialized by SDL_GetTicks()
int nr_rendered;
#ifdef SKIP_FRAMES
int nr_skipped;
#endif
};
void
fps_counter_init(struct fps_counter *counter);
void
fps_counter_start(struct fps_counter *counter);
void
fps_counter_stop(struct fps_counter *counter);
void
fps_counter_add_rendered_frame(struct fps_counter *counter);
void fps_counter_init(struct fps_counter *counter);
void fps_counter_start(struct fps_counter *counter);
void fps_counter_stop(struct fps_counter *counter);
void fps_counter_add_rendered_frame(struct fps_counter *counter);
#ifdef SKIP_FRAMES
void
fps_counter_add_skipped_frame(struct fps_counter *counter);
void fps_counter_add_skipped_frame(struct fps_counter *counter);
#endif
#endif

110
app/src/frames.c Normal file
View File

@@ -0,0 +1,110 @@
#include "frames.h"
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_mutex.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include "config.h"
#include "lock_util.h"
#include "log.h"
SDL_bool frames_init(struct frames *frames) {
if (!(frames->decoding_frame = av_frame_alloc())) {
goto error_0;
}
if (!(frames->rendering_frame = av_frame_alloc())) {
goto error_1;
}
if (!(frames->mutex = SDL_CreateMutex())) {
goto error_2;
}
#ifndef SKIP_FRAMES
if (!(frames->rendering_frame_consumed_cond = SDL_CreateCond())) {
SDL_DestroyMutex(frames->mutex);
goto error_2;
}
frames->stopped = SDL_FALSE;
#endif
// there is initially no rendering frame, so consider it has already been
// consumed
frames->rendering_frame_consumed = SDL_TRUE;
fps_counter_init(&frames->fps_counter);
return SDL_TRUE;
error_2:
av_frame_free(&frames->rendering_frame);
error_1:
av_frame_free(&frames->decoding_frame);
error_0:
return SDL_FALSE;
}
void frames_destroy(struct frames *frames) {
#ifndef SKIP_FRAMES
SDL_DestroyCond(frames->rendering_frame_consumed_cond);
#endif
SDL_DestroyMutex(frames->mutex);
av_frame_free(&frames->rendering_frame);
av_frame_free(&frames->decoding_frame);
}
static void frames_swap(struct frames *frames) {
AVFrame *tmp = frames->decoding_frame;
frames->decoding_frame = frames->rendering_frame;
frames->rendering_frame = tmp;
}
SDL_bool frames_offer_decoded_frame(struct frames *frames) {
mutex_lock(frames->mutex);
#ifndef SKIP_FRAMES
// if SKIP_FRAMES is disabled, then the decoder must wait for the current
// frame to be consumed
while (!frames->rendering_frame_consumed && !frames->stopped) {
cond_wait(frames->rendering_frame_consumed_cond, frames->mutex);
}
#else
if (frames->fps_counter.started && !frames->rendering_frame_consumed) {
fps_counter_add_skipped_frame(&frames->fps_counter);
}
#endif
frames_swap(frames);
SDL_bool previous_frame_consumed = frames->rendering_frame_consumed;
frames->rendering_frame_consumed = SDL_FALSE;
mutex_unlock(frames->mutex);
return previous_frame_consumed;
}
const AVFrame *frames_consume_rendered_frame(struct frames *frames) {
SDL_assert(!frames->rendering_frame_consumed);
frames->rendering_frame_consumed = SDL_TRUE;
if (frames->fps_counter.started) {
fps_counter_add_rendered_frame(&frames->fps_counter);
}
#ifndef SKIP_FRAMES
// if SKIP_FRAMES is disabled, then notify the decoder the current frame is
// consumed, so that it may push a new one
cond_signal(frames->rendering_frame_consumed_cond);
#endif
return frames->rendering_frame;
}
void frames_stop(struct frames *frames) {
#ifdef SKIP_FRAMES
(void) frames; // unused
#else
mutex_lock(frames->mutex);
frames->stopped = SDL_TRUE;
mutex_unlock(frames->mutex);
// wake up blocking wait
cond_signal(frames->rendering_frame_consumed_cond);
#endif
}

View File

@@ -1,8 +1,8 @@
#ifndef VIDEO_BUFFER_H
#define VIDEO_BUFFER_H
#ifndef FRAMES_H
#define FRAMES_H
#include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include "config.h"
#include "fps_counter.h"
@@ -10,40 +10,33 @@
// forward declarations
typedef struct AVFrame AVFrame;
struct video_buffer {
struct frames {
AVFrame *decoding_frame;
AVFrame *rendering_frame;
SDL_mutex *mutex;
#ifndef SKIP_FRAMES
bool interrupted;
SDL_bool stopped;
SDL_cond *rendering_frame_consumed_cond;
#endif
bool rendering_frame_consumed;
SDL_bool rendering_frame_consumed;
struct fps_counter fps_counter;
};
bool
video_buffer_init(struct video_buffer *vb);
SDL_bool frames_init(struct frames *frames);
void frames_destroy(struct frames *frames);
void
video_buffer_destroy(struct video_buffer *vb);
// set the decoded frame as ready for rendering
// set the decoder frame as ready for rendering
// this function locks frames->mutex during its execution
// the output flag is set to report whether the previous frame has been skipped
void
video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped);
// returns true if the previous frame had been consumed
SDL_bool frames_offer_decoded_frame(struct frames *frames);
// mark the rendering frame as consumed and return it
// MUST be called with frames->mutex locked!!!
// the caller is expected to render the returned frame to some texture before
// unlocking frames->mutex
const AVFrame *
video_buffer_consume_rendered_frame(struct video_buffer *vb);
const AVFrame *frames_consume_rendered_frame(struct frames *frames);
// wake up and avoid any blocking call
void
video_buffer_interrupt(struct video_buffer *vb);
void frames_stop(struct frames *frames);
#endif

View File

@@ -1,17 +1,14 @@
#include "input_manager.h"
#include <SDL2/SDL_assert.h>
#include "convert.h"
#include "lock_util.h"
#include "log.h"
// Convert window coordinates (as provided by SDL_GetMouseState() to renderer
// coordinates (as provided in SDL mouse events)
// Convert window coordinates (as provided by SDL_GetMouseState() to renderer coordinates (as provided in SDL mouse events)
//
// See my question:
// <https://stackoverflow.com/questions/49111054/how-to-get-mouse-position-on-mouse-wheel-event>
static void
convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) {
static void convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) {
SDL_Rect viewport;
float scale_x, scale_y;
SDL_RenderGetViewport(renderer, &viewport);
@@ -20,24 +17,22 @@ convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) {
*y = (int) (*y / scale_y) - viewport.y;
}
static struct point
get_mouse_point(struct screen *screen) {
static struct point get_mouse_point(struct screen *screen) {
int x;
int y;
SDL_GetMouseState(&x, &y);
convert_to_renderer_coordinates(screen->renderer, &x, &y);
SDL_assert_release(x >= 0 && x < 0x10000 && y >= 0 && y < 0x10000);
return (struct point) {
.x = x,
.y = y,
.x = (Uint16) x,
.y = (Uint16) y,
};
}
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, int actions, const char *name) {
// send DOWN event
struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_KEYCODE;
@@ -60,93 +55,58 @@ send_keycode(struct controller *controller, enum android_keycode keycode,
}
}
static inline void
action_home(struct controller *controller, int actions) {
static inline void action_home(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_HOME, actions, "HOME");
}
static inline void
action_back(struct controller *controller, int actions) {
static inline void action_back(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_BACK, actions, "BACK");
}
static inline void
action_app_switch(struct controller *controller, int actions) {
static inline void action_app_switch(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_APP_SWITCH, actions, "APP_SWITCH");
}
static inline void
action_power(struct controller *controller, int actions) {
static inline void action_power(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_POWER, actions, "POWER");
}
static inline void
action_volume_up(struct controller *controller, int actions) {
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_down(struct controller *controller, int actions) {
static inline void action_volume_down(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, actions, "VOLUME_DOWN");
}
static inline void
action_menu(struct controller *controller, int actions) {
static inline void action_menu(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
}
// turn the screen on if it was off, press BACK otherwise
static void
press_back_or_turn_screen_on(struct controller *controller) {
static void press_back_or_turn_screen_on(struct controller *controller) {
struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_COMMAND;
control_event.command_event.action =
CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON;
control_event.command_event.action = CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot turn screen on");
}
}
static void
expand_notification_panel(struct controller *controller) {
struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_COMMAND;
control_event.command_event.action =
CONTROL_EVENT_COMMAND_EXPAND_NOTIFICATION_PANEL;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot expand notification panel");
}
}
static void
collapse_notification_panel(struct controller *controller) {
struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_COMMAND;
control_event.command_event.action =
CONTROL_EVENT_COMMAND_COLLAPSE_NOTIFICATION_PANEL;
if (!controller_push_event(controller, &control_event)) {
LOGW("Cannot collapse notification panel");
}
}
static void
switch_fps_counter_state(struct video_buffer *vb) {
mutex_lock(vb->mutex);
if (vb->fps_counter.started) {
static void switch_fps_counter_state(struct frames *frames) {
mutex_lock(frames->mutex);
if (frames->fps_counter.started) {
LOGI("FPS counter stopped");
fps_counter_stop(&vb->fps_counter);
fps_counter_stop(&frames->fps_counter);
} else {
LOGI("FPS counter started");
fps_counter_start(&vb->fps_counter);
fps_counter_start(&frames->fps_counter);
}
mutex_unlock(vb->mutex);
mutex_unlock(frames->mutex);
}
static void
clipboard_paste(struct controller *controller) {
static void clipboard_paste(struct controller *controller) {
char *text = SDL_GetClipboardText();
if (!text) {
LOGW("Cannot get clipboard text: %s", SDL_GetError());
@@ -167,15 +127,8 @@ clipboard_paste(struct controller *controller) {
}
}
void
input_manager_process_text_input(struct input_manager *input_manager,
const SDL_TextInputEvent *event) {
char c = event->text[0];
if (isalpha(c) || c == ' ') {
SDL_assert(event->text[1] == '\0');
// letters and space are handled as raw key event
return;
}
void input_manager_process_text_input(struct input_manager *input_manager,
const SDL_TextInputEvent *event) {
struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_TEXT;
control_event.text_event.text = SDL_strdup(event->text);
@@ -184,116 +137,83 @@ input_manager_process_text_input(struct input_manager *input_manager,
return;
}
if (!controller_push_event(input_manager->controller, &control_event)) {
SDL_free(control_event.text_event.text);
LOGW("Cannot send text event");
}
}
void
input_manager_process_key(struct input_manager *input_manager,
const SDL_KeyboardEvent *event,
bool control) {
bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
if (alt) {
// no shortcut involves Alt or Meta, and they should not be forwarded
// to the device
return;
}
void input_manager_process_key(struct input_manager *input_manager,
const SDL_KeyboardEvent *event) {
SDL_bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
// capture all Ctrl events
if (ctrl | meta) {
if (ctrl) {
SDL_bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
if (shift) {
// currently, there is no shortcut involving SHIFT
return;
}
SDL_Keycode keycode = event->keysym.sym;
int action = event->type == SDL_KEYDOWN ? ACTION_DOWN : ACTION_UP;
bool repeat = event->repeat;
bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
SDL_bool repeat = event->repeat;
switch (keycode) {
case SDLK_h:
if (control && ctrl && !meta && !shift && !repeat) {
if (!repeat) {
action_home(input_manager->controller, action);
}
return;
case SDLK_b: // fall-through
case SDLK_BACKSPACE:
if (control && ctrl && !meta && !shift && !repeat) {
if (!repeat) {
action_back(input_manager->controller, action);
}
return;
case SDLK_s:
if (control && ctrl && !meta && !shift && !repeat) {
if (!repeat) {
action_app_switch(input_manager->controller, action);
}
return;
case SDLK_m:
if (control && ctrl && !meta && !shift && !repeat) {
if (!repeat) {
action_menu(input_manager->controller, action);
}
return;
case SDLK_p:
if (control && ctrl && !meta && !shift && !repeat) {
if (!repeat) {
action_power(input_manager->controller, action);
}
return;
case SDLK_DOWN:
#ifdef __APPLE__
if (control && !ctrl && meta && !shift) {
#else
if (control && ctrl && !meta && !shift) {
#endif
// forward repeated events
action_volume_down(input_manager->controller, action);
}
// forward repeated events
action_volume_down(input_manager->controller, action);
return;
case SDLK_UP:
#ifdef __APPLE__
if (control && !ctrl && meta && !shift) {
#else
if (control && ctrl && !meta && !shift) {
#endif
// forward repeated events
action_volume_up(input_manager->controller, action);
}
// forward repeated events
action_volume_up(input_manager->controller, action);
return;
case SDLK_v:
if (control && ctrl && !meta && !shift && !repeat
&& event->type == SDL_KEYDOWN) {
if (!repeat && event->type == SDL_KEYDOWN) {
clipboard_paste(input_manager->controller);
}
return;
case SDLK_f:
if (ctrl && !meta && !shift && !repeat
&& event->type == SDL_KEYDOWN) {
if (!repeat && event->type == SDL_KEYDOWN) {
screen_switch_fullscreen(input_manager->screen);
}
return;
case SDLK_x:
if (ctrl && !meta && !shift && !repeat
&& event->type == SDL_KEYDOWN) {
if (!repeat && event->type == SDL_KEYDOWN) {
screen_resize_to_fit(input_manager->screen);
}
return;
case SDLK_g:
if (ctrl && !meta && !shift && !repeat
&& event->type == SDL_KEYDOWN) {
if (!repeat && event->type == SDL_KEYDOWN) {
screen_resize_to_pixel_perfect(input_manager->screen);
}
return;
case SDLK_i:
if (ctrl && !meta && !shift && !repeat
&& event->type == SDL_KEYDOWN) {
switch_fps_counter_state(input_manager->video_buffer);
}
return;
case SDLK_n:
if (control && ctrl && !meta
&& !repeat && event->type == SDL_KEYDOWN) {
if (shift) {
collapse_notification_panel(input_manager->controller);
} else {
expand_notification_panel(input_manager->controller);
}
if (!repeat && event->type == SDL_KEYDOWN) {
switch_fps_counter_state(input_manager->frames);
}
return;
}
@@ -301,10 +221,6 @@ input_manager_process_key(struct input_manager *input_manager,
return;
}
if (!control) {
return;
}
struct control_event control_event;
if (input_key_from_sdl_to_android(event, &control_event)) {
if (!controller_push_event(input_manager->controller, &control_event)) {
@@ -313,73 +229,53 @@ input_manager_process_key(struct input_manager *input_manager,
}
}
void
input_manager_process_mouse_motion(struct input_manager *input_manager,
const SDL_MouseMotionEvent *event) {
void input_manager_process_mouse_motion(struct input_manager *input_manager,
const SDL_MouseMotionEvent *event) {
if (!event->state) {
// do not send motion events when no button is pressed
return;
}
struct control_event control_event;
if (mouse_motion_from_sdl_to_android(event,
input_manager->screen->frame_size,
&control_event)) {
if (mouse_motion_from_sdl_to_android(event, input_manager->screen->frame_size, &control_event)) {
if (!controller_push_event(input_manager->controller, &control_event)) {
LOGW("Cannot send mouse motion event");
}
}
}
static bool
is_outside_device_screen(struct input_manager *input_manager, int x, int y)
{
return x < 0 || x >= input_manager->screen->frame_size.width ||
y < 0 || y >= input_manager->screen->frame_size.height;
}
void
input_manager_process_mouse_button(struct input_manager *input_manager,
const SDL_MouseButtonEvent *event,
bool control) {
void input_manager_process_mouse_button(struct input_manager *input_manager,
const SDL_MouseButtonEvent *event) {
if (event->type == SDL_MOUSEBUTTONDOWN) {
if (control && event->button == SDL_BUTTON_RIGHT) {
if (event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(input_manager->controller);
return;
}
if (control && event->button == SDL_BUTTON_MIDDLE) {
if (event->button == SDL_BUTTON_MIDDLE) {
action_home(input_manager->controller, ACTION_DOWN | ACTION_UP);
return;
}
// double-click on black borders resize to fit the device screen
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
bool outside = is_outside_device_screen(input_manager,
event->x,
event->y);
if (outside) {
SDL_bool outside_device_screen =
event->x < 0 || event->x >= input_manager->screen->frame_size.width ||
event->y < 0 || event->y >= input_manager->screen->frame_size.height;
if (outside_device_screen) {
screen_resize_to_fit(input_manager->screen);
return;
}
// otherwise, send the click event to the device
}
// otherwise, send the click event to the device
}
if (!control) {
return;
}
struct control_event control_event;
if (mouse_button_from_sdl_to_android(event,
input_manager->screen->frame_size,
&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)) {
LOGW("Cannot send mouse button event");
}
}
}
void
input_manager_process_mouse_wheel(struct input_manager *input_manager,
const SDL_MouseWheelEvent *event) {
void input_manager_process_mouse_wheel(struct input_manager *input_manager,
const SDL_MouseWheelEvent *event) {
struct position position = {
.screen_size = input_manager->screen->frame_size,
.point = get_mouse_point(input_manager->screen),

View File

@@ -1,40 +1,27 @@
#ifndef INPUTMANAGER_H
#define INPUTMANAGER_H
#include <stdbool.h>
#include "common.h"
#include "controller.h"
#include "fps_counter.h"
#include "video_buffer.h"
#include "frames.h"
#include "screen.h"
struct input_manager {
struct controller *controller;
struct video_buffer *video_buffer;
struct frames *frames;
struct screen *screen;
};
void
input_manager_process_text_input(struct input_manager *input_manager,
const SDL_TextInputEvent *event);
void
input_manager_process_key(struct input_manager *input_manager,
const SDL_KeyboardEvent *event,
bool control);
void
input_manager_process_mouse_motion(struct input_manager *input_manager,
const SDL_MouseMotionEvent *event);
void
input_manager_process_mouse_button(struct input_manager *input_manager,
const SDL_MouseButtonEvent *event,
bool control);
void
input_manager_process_mouse_wheel(struct input_manager *input_manager,
const SDL_MouseWheelEvent *event);
void input_manager_process_text_input(struct input_manager *input_manager,
const SDL_TextInputEvent *event);
void input_manager_process_key(struct input_manager *input_manager,
const SDL_KeyboardEvent *event);
void input_manager_process_mouse_motion(struct input_manager *input_manager,
const SDL_MouseMotionEvent *event);
void input_manager_process_mouse_button(struct input_manager *input_manager,
const SDL_MouseButtonEvent *event);
void input_manager_process_mouse_wheel(struct input_manager *input_manager,
const SDL_MouseWheelEvent *event);
#endif

View File

@@ -4,32 +4,28 @@
#include "log.h"
void
mutex_lock(SDL_mutex *mutex) {
void mutex_lock(SDL_mutex *mutex) {
if (SDL_LockMutex(mutex)) {
LOGC("Could not lock mutex");
abort();
}
}
void
mutex_unlock(SDL_mutex *mutex) {
void mutex_unlock(SDL_mutex *mutex) {
if (SDL_UnlockMutex(mutex)) {
LOGC("Could not unlock mutex");
abort();
}
}
void
cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
void cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
if (SDL_CondWait(cond, mutex)) {
LOGC("Could not wait on condition");
abort();
}
}
void
cond_signal(SDL_cond *cond) {
void cond_signal(SDL_cond *cond) {
if (SDL_CondSignal(cond)) {
LOGC("Could not signal a condition");
abort();

View File

@@ -5,16 +5,9 @@
typedef struct SDL_mutex SDL_mutex;
typedef struct SDL_cond SDL_cond;
void
mutex_lock(SDL_mutex *mutex);
void
mutex_unlock(SDL_mutex *mutex);
void
cond_wait(SDL_cond *cond, SDL_mutex *mutex);
void
cond_signal(SDL_cond *cond);
void mutex_lock(SDL_mutex *mutex);
void mutex_unlock(SDL_mutex *mutex);
void cond_wait(SDL_cond *cond, SDL_mutex *mutex);
void cond_signal(SDL_cond *cond);
#endif

View File

@@ -3,7 +3,6 @@
#include <SDL2/SDL_log.h>
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGW(...) SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)

View File

@@ -1,33 +1,23 @@
#include "scrcpy.h"
#include <getopt.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include <libavformat/avformat.h>
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
#include <SDL2/SDL.h>
#include "compat.h"
#include "config.h"
#include "log.h"
#include "recorder.h"
struct args {
const char *serial;
const char *crop;
const char *record_filename;
enum recorder_format record_format;
bool fullscreen;
bool no_control;
bool no_display;
bool help;
bool version;
bool show_touches;
uint16_t port;
uint16_t max_size;
uint32_t bit_rate;
bool always_on_top;
SDL_bool fullscreen;
SDL_bool help;
SDL_bool version;
SDL_bool show_touches;
Uint16 port;
Uint16 max_size;
Uint32 bit_rate;
};
static void usage(const char *arg0) {
@@ -50,9 +40,6 @@ static void usage(const char *arg0) {
" -f, --fullscreen\n"
" Start in fullscreen.\n"
"\n"
" -F, --record-format\n"
" Force recording format (either mp4 or mkv).\n"
"\n"
" -h, --help\n"
" Print this help.\n"
"\n"
@@ -62,22 +49,10 @@ static void usage(const char *arg0) {
" is preserved.\n"
" Default is %d%s.\n"
"\n"
" -n, --no-control\n"
" Disable device control (mirror the device in read-only).\n"
"\n"
" -N, --no-display\n"
" Do not display device (only when screen recording is\n"
" enabled).\n"
"\n"
" -p, --port port\n"
" Set the TCP port the client listens on.\n"
" Default is %d.\n"
"\n"
" -r, --record file.mp4\n"
" Record screen to file.\n"
" The format is determined by the -F/--record-format option if\n"
" set, or by the file extension (.mp4 or .mkv).\n"
"\n"
" -s, --serial\n"
" The device serial number. Mandatory only if several devices\n"
" are connected to adb.\n"
@@ -86,9 +61,6 @@ static void usage(const char *arg0) {
" Enable \"show touches\" on start, disable on quit.\n"
" It only shows physical touches (not clicks from scrcpy).\n"
"\n"
" -T, --always-on-top\n"
" Make scrcpy window always on top (above other windows).\n"
"\n"
" -v, --version\n"
" Print the version of scrcpy.\n"
"\n"
@@ -105,6 +77,7 @@ static void usage(const char *arg0) {
" resize window to remove black borders\n"
"\n"
" Ctrl+h\n"
" Home\n"
" Middle-click\n"
" click on HOME\n"
"\n"
@@ -131,12 +104,6 @@ static void usage(const char *arg0) {
" Right-click (when screen is off)\n"
" turn screen on\n"
"\n"
" Ctrl+n\n"
" expand notification panel\n"
"\n"
" Ctrl+Shift+n\n"
" collapse notification panel\n"
"\n"
" Ctrl+v\n"
" paste computer clipboard to device\n"
"\n"
@@ -152,37 +119,28 @@ static void usage(const char *arg0) {
DEFAULT_LOCAL_PORT);
}
static void
print_version(void) {
fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION);
static void print_version(void) {
fprintf(stderr, "scrcpy v%s\n\n", SCRCPY_VERSION);
fprintf(stderr, "dependencies:\n");
fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION,
SDL_PATCHLEVEL);
fprintf(stderr, " - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR,
LIBAVCODEC_VERSION_MINOR,
LIBAVCODEC_VERSION_MICRO);
fprintf(stderr, " - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR,
LIBAVFORMAT_VERSION_MINOR,
LIBAVFORMAT_VERSION_MICRO);
fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
LIBAVUTIL_VERSION_MINOR,
LIBAVUTIL_VERSION_MICRO);
fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL);
fprintf(stderr, " - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR, LIBAVCODEC_VERSION_MINOR, LIBAVCODEC_VERSION_MICRO);
fprintf(stderr, " - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO);
fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR, LIBAVUTIL_VERSION_MINOR, LIBAVUTIL_VERSION_MICRO);
}
static bool
parse_bit_rate(char *optarg, uint32_t *bit_rate) {
static SDL_bool parse_bit_rate(char *optarg, Uint32 *bit_rate) {
char *endptr;
if (*optarg == '\0') {
LOGE("Bit-rate parameter is empty");
return false;
return SDL_FALSE;
}
long value = strtol(optarg, &endptr, 0);
int mul = 1;
if (*endptr != '\0') {
if (optarg == endptr) {
LOGE("Invalid bit-rate: %s", optarg);
return false;
return SDL_FALSE;
}
if ((*endptr == 'M' || *endptr == 'm') && endptr[1] == '\0') {
mul = 1000000;
@@ -190,204 +148,122 @@ parse_bit_rate(char *optarg, uint32_t *bit_rate) {
mul = 1000;
} else {
LOGE("Invalid bit-rate unit: %s", optarg);
return false;
return SDL_FALSE;
}
}
if (value < 0 || ((uint32_t) -1) / mul < value) {
if (value < 0 || ((Uint32) -1) / mul < value) {
LOGE("Bitrate must be positive and less than 2^32: %s", optarg);
return false;
return SDL_FALSE;
}
*bit_rate = (uint32_t) value * mul;
return true;
*bit_rate = (Uint32) value * mul;
return SDL_TRUE;
}
static bool
parse_max_size(char *optarg, uint16_t *max_size) {
static SDL_bool parse_max_size(char *optarg, Uint16 *max_size) {
char *endptr;
if (*optarg == '\0') {
LOGE("Max size parameter is empty");
return false;
return SDL_FALSE;
}
long value = strtol(optarg, &endptr, 0);
if (*endptr != '\0') {
LOGE("Invalid max size: %s", optarg);
return false;
return SDL_FALSE;
}
if (value & ~0xffff) {
LOGE("Max size must be between 0 and 65535: %ld", value);
return false;
return SDL_FALSE;
}
*max_size = (uint16_t) value;
return true;
*max_size = (Uint16) value;
return SDL_TRUE;
}
static bool
parse_port(char *optarg, uint16_t *port) {
static SDL_bool parse_port(char *optarg, Uint16 *port) {
char *endptr;
if (*optarg == '\0') {
LOGE("Invalid port parameter is empty");
return false;
return SDL_FALSE;
}
long value = strtol(optarg, &endptr, 0);
if (*endptr != '\0') {
LOGE("Invalid port: %s", optarg);
return false;
return SDL_FALSE;
}
if (value & ~0xffff) {
LOGE("Port out of range: %ld", value);
return false;
return SDL_FALSE;
}
*port = (uint16_t) value;
return true;
*port = (Uint16) value;
return SDL_TRUE;
}
static bool
parse_record_format(const char *optarg, enum recorder_format *format) {
if (!strcmp(optarg, "mp4")) {
*format = RECORDER_FORMAT_MP4;
return true;
}
if (!strcmp(optarg, "mkv")) {
*format = RECORDER_FORMAT_MKV;
return true;
}
LOGE("Unsupported format: %s (expected mp4 or mkv)", optarg);
return false;
}
static enum recorder_format
guess_record_format(const char *filename) {
size_t len = strlen(filename);
if (len < 4) {
return 0;
}
const char *ext = &filename[len - 4];
if (!strcmp(ext, ".mp4")) {
return RECORDER_FORMAT_MP4;
}
if (!strcmp(ext, ".mkv")) {
return RECORDER_FORMAT_MKV;
}
return 0;
}
static bool
parse_args(struct args *args, int argc, char *argv[]) {
static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
static const struct option long_options[] = {
{"always-on-top", no_argument, NULL, 'T'},
{"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'},
{"no-control", no_argument, NULL, 'n'},
{"no-display", no_argument, NULL, 'N'},
{"port", required_argument, NULL, 'p'},
{"record", required_argument, NULL, 'r'},
{"record-format", required_argument, NULL, 'f'},
{"serial", required_argument, NULL, 's'},
{"show-touches", no_argument, NULL, 't'},
{"version", no_argument, NULL, 'v'},
{NULL, 0, NULL, 0 },
{"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 },
};
int c;
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:tTv", long_options,
NULL)) != -1) {
while ((c = getopt_long(argc, argv, "b:c:fhm:p:s:tv", long_options, NULL)) != -1) {
switch (c) {
case 'b':
if (!parse_bit_rate(optarg, &args->bit_rate)) {
return false;
return SDL_FALSE;
}
break;
case 'c':
args->crop = optarg;
break;
case 'f':
args->fullscreen = true;
break;
case 'F':
if (!parse_record_format(optarg, &args->record_format)) {
return false;
}
args->fullscreen = SDL_TRUE;
break;
case 'h':
args->help = true;
args->help = SDL_TRUE;
break;
case 'm':
if (!parse_max_size(optarg, &args->max_size)) {
return false;
return SDL_FALSE;
}
break;
case 'n':
args->no_control = true;
break;
case 'N':
args->no_display = true;
break;
case 'p':
if (!parse_port(optarg, &args->port)) {
return false;
return SDL_FALSE;
}
break;
case 'r':
args->record_filename = optarg;
break;
case 's':
args->serial = optarg;
break;
case 't':
args->show_touches = true;
break;
case 'T':
args->always_on_top = true;
args->show_touches = SDL_TRUE;
break;
case 'v':
args->version = true;
args->version = SDL_TRUE;
break;
default:
// getopt prints the error message on stderr
return false;
return SDL_FALSE;
}
}
if (args->no_display && !args->record_filename) {
LOGE("-N/--no-display requires screen recording (-r/--record)");
return false;
}
if (args->no_display && args->fullscreen) {
LOGE("-f/--fullscreen-window is incompatible with -N/--no-display");
return false;
}
int index = optind;
if (index < argc) {
LOGE("Unexpected additional argument: %s", argv[index]);
return false;
return SDL_FALSE;
}
if (args->record_format && !args->record_filename) {
LOGE("Record format specified without recording");
return false;
}
if (args->record_filename && !args->record_format) {
args->record_format = guess_record_format(args->record_filename);
if (!args->record_format) {
LOGE("No format specified for \"%s\" (try with -F mkv)",
args->record_filename);
return false;
}
}
return true;
return SDL_TRUE;
}
int
main(int argc, char *argv[]) {
int main(int argc, char *argv[]) {
#ifdef __WINDOWS__
// disable buffering, we want logs immediately
// even line buffering (setvbuf() with mode _IOLBF) is not sufficient
@@ -397,17 +273,12 @@ main(int argc, char *argv[]) {
struct args args = {
.serial = NULL,
.crop = NULL,
.record_filename = NULL,
.record_format = 0,
.help = false,
.version = false,
.show_touches = false,
.help = SDL_FALSE,
.version = SDL_FALSE,
.show_touches = SDL_FALSE,
.port = DEFAULT_LOCAL_PORT,
.max_size = DEFAULT_MAX_SIZE,
.bit_rate = DEFAULT_BIT_RATE,
.always_on_top = false,
.no_control = false,
.no_display = false,
};
if (!parse_args(&args, argc, argv)) {
return 1;
@@ -423,7 +294,7 @@ main(int argc, char *argv[]) {
return 0;
}
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
av_register_all();
#endif
@@ -439,25 +310,14 @@ main(int argc, char *argv[]) {
.serial = args.serial,
.crop = args.crop,
.port = args.port,
.record_filename = args.record_filename,
.record_format = args.record_format,
.max_size = args.max_size,
.bit_rate = args.bit_rate,
.show_touches = args.show_touches,
.fullscreen = args.fullscreen,
.always_on_top = args.always_on_top,
.no_control = args.no_control,
.no_display = args.no_display,
};
int res = scrcpy(&options) ? 0 : 1;
avformat_network_deinit(); // ignore failure
#if defined (__WINDOWS__) && ! defined (WINDOWS_NOCONSOLE)
if (res != 0) {
fprintf(stderr, "Press any key to continue...\n");
getchar();
}
#endif
return res;
}

View File

@@ -18,8 +18,7 @@
typedef struct in_addr IN_ADDR;
#endif
socket_t
net_connect(uint32_t addr, uint16_t port) {
socket_t net_connect(Uint32 addr, Uint16 port) {
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
perror("socket");
@@ -39,8 +38,7 @@ net_connect(uint32_t addr, uint16_t port) {
return sock;
}
socket_t
net_listen(uint32_t addr, uint16_t port, int backlog) {
socket_t net_listen(Uint32 addr, Uint16 port, int backlog) {
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
perror("socket");
@@ -48,8 +46,7 @@ net_listen(uint32_t addr, uint16_t port, int backlog) {
}
int reuse = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
sizeof(reuse)) == -1) {
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse, sizeof(reuse)) == -1) {
perror("setsockopt(SO_REUSEADDR)");
}
@@ -71,30 +68,25 @@ net_listen(uint32_t addr, uint16_t port, int backlog) {
return sock;
}
socket_t
net_accept(socket_t server_socket) {
socket_t net_accept(socket_t server_socket) {
SOCKADDR_IN csin;
socklen_t sinsize = sizeof(csin);
return accept(server_socket, (SOCKADDR *) &csin, &sinsize);
}
ssize_t
net_recv(socket_t socket, void *buf, size_t len) {
ssize_t net_recv(socket_t socket, void *buf, size_t len) {
return recv(socket, buf, len, 0);
}
ssize_t
net_recv_all(socket_t socket, void *buf, size_t len) {
ssize_t net_recv_all(socket_t socket, void *buf, size_t len) {
return recv(socket, buf, len, MSG_WAITALL);
}
ssize_t
net_send(socket_t socket, const void *buf, size_t len) {
ssize_t net_send(socket_t socket, const void *buf, size_t len) {
return send(socket, buf, len, 0);
}
ssize_t
net_send_all(socket_t socket, const void *buf, size_t len) {
ssize_t net_send_all(socket_t socket, const void *buf, size_t len) {
ssize_t w = 0;
while (len > 0) {
w = send(socket, buf, len, 0);
@@ -107,7 +99,6 @@ net_send_all(socket_t socket, const void *buf, size_t len) {
return w;
}
bool
net_shutdown(socket_t socket, int how) {
SDL_bool net_shutdown(socket_t socket, int how) {
return !shutdown(socket, how);
}

View File

@@ -1,9 +1,8 @@
#ifndef NET_H
#define NET_H
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_platform.h>
#include <SDL2/SDL_stdinc.h>
#ifdef __WINDOWS__
# include <winsock2.h>
@@ -17,39 +16,20 @@
typedef int socket_t;
#endif
bool
net_init(void);
SDL_bool net_init(void);
void net_cleanup(void);
void
net_cleanup(void);
socket_t
net_connect(uint32_t addr, uint16_t port);
socket_t
net_listen(uint32_t addr, uint16_t port, int backlog);
socket_t
net_accept(socket_t server_socket);
socket_t net_connect(Uint32 addr, Uint16 port);
socket_t net_listen(Uint32 addr, Uint16 port, int backlog);
socket_t net_accept(socket_t server_socket);
// the _all versions wait/retry until len bytes have been written/read
ssize_t
net_recv(socket_t socket, void *buf, size_t len);
ssize_t
net_recv_all(socket_t socket, void *buf, size_t len);
ssize_t
net_send(socket_t socket, const void *buf, size_t len);
ssize_t
net_send_all(socket_t socket, const void *buf, size_t len);
ssize_t net_recv(socket_t socket, void *buf, size_t len);
ssize_t net_recv_all(socket_t socket, void *buf, size_t len);
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);
// how is SHUT_RD (read), SHUT_WR (write) or SHUT_RDWR (both)
bool
net_shutdown(socket_t socket, int how);
bool
net_close(socket_t socket);
SDL_bool net_shutdown(socket_t socket, int how);
SDL_bool net_close(socket_t socket);
#endif

View File

@@ -1,181 +0,0 @@
#include "recorder.h"
#include <libavutil/time.h>
#include <SDL2/SDL_assert.h>
#include "compat.h"
#include "config.h"
#include "log.h"
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
static const AVOutputFormat *
find_muxer(const char *name) {
#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
void *opaque = NULL;
#endif
const AVOutputFormat *oformat = NULL;
do {
#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
oformat = av_muxer_iterate(&opaque);
#else
oformat = av_oformat_next(oformat);
#endif
// until null or with name "mp4"
} while (oformat && strcmp(oformat->name, name));
return oformat;
}
bool
recorder_init(struct recorder *recorder,
const char *filename,
enum recorder_format format,
struct size declared_frame_size) {
recorder->filename = SDL_strdup(filename);
if (!recorder->filename) {
LOGE("Cannot strdup filename");
return false;
}
recorder->format = format;
recorder->declared_frame_size = declared_frame_size;
recorder->header_written = false;
return true;
}
void
recorder_destroy(struct recorder *recorder) {
SDL_free(recorder->filename);
}
static const char *
recorder_get_format_name(enum recorder_format format) {
switch (format) {
case RECORDER_FORMAT_MP4: return "mp4";
case RECORDER_FORMAT_MKV: return "matroska";
default: return NULL;
}
}
bool
recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
const char *format_name = recorder_get_format_name(recorder->format);
SDL_assert(format_name);
const AVOutputFormat *format = find_muxer(format_name);
if (!format) {
LOGE("Could not find muxer");
return false;
}
recorder->ctx = avformat_alloc_context();
if (!recorder->ctx) {
LOGE("Could not allocate output context");
return false;
}
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
// returns (on purpose) a pointer-to-const, but AVFormatContext.oformat
// still expects a pointer-to-non-const (it has not be updated accordingly)
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
recorder->ctx->oformat = (AVOutputFormat *) format;
AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec);
if (!ostream) {
avformat_free_context(recorder->ctx);
return false;
}
#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
ostream->codecpar->codec_id = input_codec->id;
ostream->codecpar->format = AV_PIX_FMT_YUV420P;
ostream->codecpar->width = recorder->declared_frame_size.width;
ostream->codecpar->height = recorder->declared_frame_size.height;
#else
ostream->codec->codec_type = AVMEDIA_TYPE_VIDEO;
ostream->codec->codec_id = input_codec->id;
ostream->codec->pix_fmt = AV_PIX_FMT_YUV420P;
ostream->codec->width = recorder->declared_frame_size.width;
ostream->codec->height = recorder->declared_frame_size.height;
#endif
int ret = avio_open(&recorder->ctx->pb, recorder->filename,
AVIO_FLAG_WRITE);
if (ret < 0) {
LOGE("Failed to open output file: %s", recorder->filename);
// ostream will be cleaned up during context cleaning
avformat_free_context(recorder->ctx);
return false;
}
LOGI("Recording started to %s file: %s", format_name, recorder->filename);
return true;
}
void
recorder_close(struct recorder *recorder) {
int ret = av_write_trailer(recorder->ctx);
if (ret < 0) {
LOGE("Failed to write trailer to %s", recorder->filename);
}
avio_close(recorder->ctx->pb);
avformat_free_context(recorder->ctx);
const char *format_name = recorder_get_format_name(recorder->format);
LOGI("Recording complete to %s file: %s", format_name, recorder->filename);
}
static bool
recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
AVStream *ostream = recorder->ctx->streams[0];
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
if (!extradata) {
LOGC("Cannot allocate extradata");
return false;
}
// copy the first packet to the extra data
memcpy(extradata, packet->data, packet->size);
#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
ostream->codecpar->extradata = extradata;
ostream->codecpar->extradata_size = packet->size;
#else
ostream->codec->extradata = extradata;
ostream->codec->extradata_size = packet->size;
#endif
int ret = avformat_write_header(recorder->ctx, NULL);
if (ret < 0) {
LOGE("Failed to write header to %s", recorder->filename);
SDL_free(extradata);
avio_closep(&recorder->ctx->pb);
avformat_free_context(recorder->ctx);
return false;
}
return true;
}
static void
recorder_rescale_packet(struct recorder *recorder, AVPacket *packet) {
AVStream *ostream = recorder->ctx->streams[0];
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
}
bool
recorder_write(struct recorder *recorder, AVPacket *packet) {
if (!recorder->header_written) {
bool ok = recorder_write_header(recorder, packet);
if (!ok) {
return false;
}
recorder->header_written = true;
}
recorder_rescale_packet(recorder, packet);
return av_write_frame(recorder->ctx, packet) >= 0;
}

View File

@@ -1,38 +0,0 @@
#ifndef RECORDER_H
#define RECORDER_H
#include <stdbool.h>
#include <libavformat/avformat.h>
#include "common.h"
enum recorder_format {
RECORDER_FORMAT_MP4 = 1,
RECORDER_FORMAT_MKV,
};
struct recorder {
char *filename;
enum recorder_format format;
AVFormatContext *ctx;
struct size declared_frame_size;
bool header_written;
};
bool
recorder_init(struct recorder *recoder, const char *filename,
enum recorder_format format, struct size declared_frame_size);
void
recorder_destroy(struct recorder *recorder);
bool
recorder_open(struct recorder *recorder, const AVCodec *input_codec);
void
recorder_close(struct recorder *recorder);
bool
recorder_write(struct recorder *recorder, AVPacket *packet);
#endif

View File

@@ -9,80 +9,34 @@
#include "command.h"
#include "common.h"
#include "compat.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 "log.h"
#include "lock_util.h"
#include "net.h"
#include "recorder.h"
#include "screen.h"
#include "server.h"
#include "stream.h"
#include "tiny_xpm.h"
#include "video_buffer.h"
static struct server server = SERVER_INITIALIZER;
static struct screen screen = SCREEN_INITIALIZER;
static struct video_buffer video_buffer;
static struct stream stream;
static struct frames frames;
static struct decoder decoder;
static struct recorder recorder;
static struct controller controller;
static struct file_handler file_handler;
static struct input_manager input_manager = {
.controller = &controller,
.video_buffer = &video_buffer,
.frames = &frames,
.screen = &screen,
};
// init SDL and set appropriate hints
static bool
sdl_init_and_configure(bool display) {
uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS;
if (SDL_Init(flags)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
return false;
}
atexit(SDL_Quit);
if (!display) {
return true;
}
// Use the best available scale quality
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2")) {
LOGW("Could not enable bilinear filtering");
}
#ifdef SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
// Handle a click to gain focus as any other click
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
LOGW("Could not enable mouse focus clickthrough");
}
#endif
#ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
// Disable compositor bypassing on X11
if (!SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0")) {
LOGW("Could not disable X11 compositor bypass");
}
#endif
// Do not disable the screensaver when scrcpy is running
SDL_EnableScreenSaver();
return true;
}
#if defined(__APPLE__) || defined(__WINDOWS__)
# define CONTINUOUS_RESIZING_WORKAROUND
#endif
@@ -93,10 +47,8 @@ sdl_init_and_configure(bool display) {
//
// <https://bugzilla.libsdl.org/show_bug.cgi?id=2077>
// <https://stackoverflow.com/a/40693139/1987178>
static int
event_watcher(void *data, SDL_Event *event) {
if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
static int event_watcher(void *data, SDL_Event *event) {
if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_RESIZED) {
// called from another thread, not very safe, but it's a workaround!
screen_render(&screen);
}
@@ -104,117 +56,76 @@ event_watcher(void *data, SDL_Event *event) {
}
#endif
static bool
is_apk(const char *file) {
static SDL_bool is_apk(const char *file) {
const char *ext = strrchr(file, '.');
return ext && !strcmp(ext, ".apk");
}
enum event_result {
EVENT_RESULT_CONTINUE,
EVENT_RESULT_STOPPED_BY_USER,
EVENT_RESULT_STOPPED_BY_EOS,
};
static enum event_result
handle_event(SDL_Event *event, bool control) {
switch (event->type) {
case EVENT_STREAM_STOPPED:
LOGD("Video stream stopped");
return EVENT_RESULT_STOPPED_BY_EOS;
case SDL_QUIT:
LOGD("User requested to quit");
return EVENT_RESULT_STOPPED_BY_USER;
case EVENT_NEW_FRAME:
if (!screen.has_frame) {
screen.has_frame = true;
// this is the very first frame, show the window
screen_show_window(&screen);
}
if (!screen_update_frame(&screen, &video_buffer)) {
return false;
}
break;
case SDL_WINDOWEVENT:
switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(&screen);
break;
}
break;
case SDL_TEXTINPUT:
if (!control) {
break;
}
input_manager_process_text_input(&input_manager, &event->text);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
// event even if control is disabled
input_manager_process_key(&input_manager, &event->key, control);
break;
case SDL_MOUSEMOTION:
if (!control) {
break;
}
input_manager_process_mouse_motion(&input_manager, &event->motion);
break;
case SDL_MOUSEWHEEL:
if (!control) {
break;
}
input_manager_process_mouse_wheel(&input_manager, &event->wheel);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process
// the event even if control is disabled
input_manager_process_mouse_button(&input_manager, &event->button,
control);
break;
case SDL_DROPFILE: {
if (!control) {
break;
}
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 EVENT_RESULT_CONTINUE;
}
static bool
event_loop(bool display, bool control) {
static SDL_bool event_loop(void) {
#ifdef CONTINUOUS_RESIZING_WORKAROUND
if (display) {
SDL_AddEventWatch(event_watcher, NULL);
}
SDL_AddEventWatch(event_watcher, NULL);
#endif
SDL_Event event;
while (SDL_WaitEvent(&event)) {
enum event_result result = handle_event(&event, control);
switch (result) {
case EVENT_RESULT_STOPPED_BY_USER:
return true;
case EVENT_RESULT_STOPPED_BY_EOS:
return false;
case EVENT_RESULT_CONTINUE:
switch (event.type) {
case EVENT_DECODER_STOPPED:
LOGD("Video decoder stopped");
return SDL_FALSE;
case SDL_QUIT:
LOGD("User requested to quit");
return SDL_TRUE;
case EVENT_NEW_FRAME:
if (!screen.has_frame) {
screen.has_frame = SDL_TRUE;
// this is the very first frame, show the window
screen_show_window(&screen);
}
if (!screen_update_frame(&screen, &frames)) {
return SDL_FALSE;
}
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;
}
break;
case SDL_TEXTINPUT:
input_manager_process_text_input(&input_manager, &event.text);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
input_manager_process_key(&input_manager, &event.key);
break;
case SDL_MOUSEMOTION:
input_manager_process_mouse_motion(&input_manager, &event.motion);
break;
case SDL_MOUSEWHEEL:
input_manager_process_mouse_wheel(&input_manager, &event.wheel);
break;
case SDL_MOUSEBUTTONDOWN:
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 false;
return SDL_FALSE;
}
static process_t
set_show_touches_enabled(const char *serial, bool enabled) {
static process_t set_show_touches_enabled(const char *serial, SDL_bool enabled) {
const char *value = enabled ? "1" : "0";
const char *const adb_cmd[] = {
"shell", "settings", "put", "system", "show_touches", value
@@ -222,197 +133,122 @@ set_show_touches_enabled(const char *serial, bool enabled) {
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
static void
wait_show_touches(process_t process) {
static void wait_show_touches(process_t process) {
// reap the process, ignore the result
process_check_success(process, "show_touches");
}
static SDL_LogPriority
sdl_priority_from_av_level(int level) {
switch (level) {
case AV_LOG_PANIC:
case AV_LOG_FATAL:
return SDL_LOG_PRIORITY_CRITICAL;
case AV_LOG_ERROR:
return SDL_LOG_PRIORITY_ERROR;
case AV_LOG_WARNING:
return SDL_LOG_PRIORITY_WARN;
case AV_LOG_INFO:
return SDL_LOG_PRIORITY_INFO;
}
// do not forward others, which are too verbose
return 0;
}
static void
av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
SDL_LogPriority priority = sdl_priority_from_av_level(level);
if (priority == 0) {
return;
}
char *local_fmt = SDL_malloc(strlen(fmt) + 10);
if (!local_fmt) {
LOGC("Cannot allocate string");
return;
}
// strcpy is safe here, the destination is large enough
strcpy(local_fmt, "[FFmpeg] ");
strcpy(local_fmt + 9, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
SDL_free(local_fmt);
}
bool
scrcpy(const struct scrcpy_options *options) {
bool record = !!options->record_filename;
SDL_bool scrcpy(const struct scrcpy_options *options) {
if (!server_start(&server, options->serial, options->port,
options->max_size, options->bit_rate, options->crop,
record)) {
return false;
options->max_size, options->bit_rate, options->crop)) {
return SDL_FALSE;
}
process_t proc_show_touches = PROCESS_NONE;
bool show_touches_waited;
SDL_bool show_touches_waited;
if (options->show_touches) {
LOGI("Enable show_touches");
proc_show_touches = set_show_touches_enabled(options->serial, true);
show_touches_waited = false;
proc_show_touches = set_show_touches_enabled(options->serial, SDL_TRUE);
show_touches_waited = SDL_FALSE;
}
bool ret = true;
SDL_bool ret = SDL_TRUE;
bool display = !options->no_display;
bool control = !options->no_control;
if (!SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1")) {
LOGW("Cannot request to keep default signal handlers");
}
if (!sdl_init_and_configure(display)) {
ret = false;
if (!sdl_init_and_configure()) {
ret = SDL_FALSE;
goto finally_destroy_server;
}
if (!server_connect_to(&server)) {
socket_t device_socket = server_connect_to(&server);
if (device_socket == INVALID_SOCKET) {
server_stop(&server);
ret = false;
ret = SDL_FALSE;
goto finally_destroy_server;
}
char device_name[DEVICE_NAME_FIELD_LENGTH];
struct size frame_size;
// screenrecord does not send frames when the screen content does not
// change therefore, we transmit the screen size before the video stream,
// to be able to init the window immediately
if (!device_read_info(server.video_socket, device_name, &frame_size)) {
// screenrecord does not send frames when the screen content does not change
// therefore, we transmit the screen size before the video stream, to be able
// to init the window immediately
if (!device_read_info(device_socket, device_name, &frame_size)) {
server_stop(&server);
ret = false;
ret = SDL_FALSE;
goto finally_destroy_server;
}
struct decoder *dec = NULL;
if (display) {
if (!video_buffer_init(&video_buffer)) {
server_stop(&server);
ret = false;
goto finally_destroy_server;
}
if (control && !file_handler_init(&file_handler, server.serial)) {
ret = false;
server_stop(&server);
goto finally_destroy_video_buffer;
}
decoder_init(&decoder, &video_buffer);
dec = &decoder;
if (!frames_init(&frames)) {
server_stop(&server);
ret = SDL_FALSE;
goto finally_destroy_server;
}
struct recorder *rec = NULL;
if (record) {
if (!recorder_init(&recorder,
options->record_filename,
options->record_format,
frame_size)) {
ret = false;
server_stop(&server);
goto finally_destroy_file_handler;
}
rec = &recorder;
if (!file_handler_init(&file_handler, server.serial)) {
ret = SDL_FALSE;
server_stop(&server);
goto finally_destroy_frames;
}
av_log_set_callback(av_log_callback);
stream_init(&stream, server.video_socket, dec, rec);
decoder_init(&decoder, &frames, device_socket);
// now we consumed the header values, the socket receives the video stream
// start the stream
if (!stream_start(&stream)) {
ret = false;
// start the decoder
if (!decoder_start(&decoder)) {
ret = SDL_FALSE;
server_stop(&server);
goto finally_destroy_recorder;
goto finally_destroy_file_handler;
}
if (display) {
if (control) {
if (!controller_init(&controller, server.control_socket)) {
ret = false;
goto finally_stop_stream;
}
if (!controller_init(&controller, device_socket)) {
ret = SDL_FALSE;
goto finally_stop_decoder;
}
if (!controller_start(&controller)) {
ret = false;
goto finally_destroy_controller;
}
}
if (!controller_start(&controller)) {
ret = SDL_FALSE;
goto finally_destroy_controller;
}
if (!screen_init_rendering(&screen, device_name, frame_size,
options->always_on_top)) {
ret = false;
goto finally_stop_and_join_controller;
}
if (options->fullscreen) {
screen_switch_fullscreen(&screen);
}
if (!screen_init_rendering(&screen, device_name, frame_size)) {
ret = SDL_FALSE;
goto finally_stop_and_join_controller;
}
if (options->show_touches) {
wait_show_touches(proc_show_touches);
show_touches_waited = true;
show_touches_waited = SDL_TRUE;
}
ret = event_loop(display, control);
if (options->fullscreen) {
screen_switch_fullscreen(&screen);
}
ret = event_loop();
LOGD("quit...");
screen_destroy(&screen);
finally_stop_and_join_controller:
if (display && control) {
controller_stop(&controller);
controller_join(&controller);
}
controller_stop(&controller);
controller_join(&controller);
finally_destroy_controller:
if (display && control) {
controller_destroy(&controller);
}
finally_stop_stream:
stream_stop(&stream);
// stop the server before stream_join() to wake up the stream
controller_destroy(&controller);
finally_stop_decoder:
decoder_stop(&decoder);
// stop the server before decoder_join() to wake up the decoder
server_stop(&server);
stream_join(&stream);
finally_destroy_recorder:
if (record) {
recorder_destroy(&recorder);
}
decoder_join(&decoder);
finally_destroy_file_handler:
if (display && control) {
file_handler_stop(&file_handler);
file_handler_join(&file_handler);
file_handler_destroy(&file_handler);
}
finally_destroy_video_buffer:
if (display) {
video_buffer_destroy(&video_buffer);
}
file_handler_stop(&file_handler);
file_handler_join(&file_handler);
file_handler_destroy(&file_handler);
finally_destroy_frames:
frames_destroy(&frames);
finally_destroy_server:
if (options->show_touches) {
if (!show_touches_waited) {
@@ -420,8 +256,7 @@ finally_destroy_server:
wait_show_touches(proc_show_touches);
}
LOGI("Disable show_touches");
proc_show_touches = set_show_touches_enabled(options->serial,
false);
proc_show_touches = set_show_touches_enabled(options->serial, SDL_FALSE);
wait_show_touches(proc_show_touches);
}

View File

@@ -1,26 +1,18 @@
#ifndef SCRCPY_H
#define SCRCPY_H
#include <stdbool.h>
#include <stdint.h>
#include <recorder.h>
#include <SDL2/SDL_stdinc.h>
struct scrcpy_options {
const char *serial;
const char *crop;
const char *record_filename;
enum recorder_format record_format;
uint16_t port;
uint16_t max_size;
uint32_t bit_rate;
bool show_touches;
bool fullscreen;
bool always_on_top;
bool no_control;
bool no_display;
Uint16 port;
Uint16 max_size;
Uint32 bit_rate;
SDL_bool show_touches;
SDL_bool fullscreen;
};
bool
scrcpy(const struct scrcpy_options *options);
SDL_bool scrcpy(const struct scrcpy_options *options);
#endif

View File

@@ -1,21 +1,40 @@
#include "screen.h"
#include <string.h>
#include <SDL2/SDL.h>
#include <string.h>
#include "common.h"
#include "compat.h"
#include "icon.xpm"
#include "lock_util.h"
#include "log.h"
#include "tiny_xpm.h"
#include "video_buffer.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());
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");
}
#if SDL_VERSION_ATLEAST(2, 0, 5)
// Handle a click to gain focus as any other click
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
LOGW("Could not enable mouse focus clickthrough");
}
#endif
return SDL_TRUE;
}
// get the window size in a struct size
static struct size
get_native_window_size(SDL_Window *window) {
static struct size get_native_window_size(SDL_Window *window) {
int width;
int height;
SDL_GetWindowSize(window, &width, &height);
@@ -27,8 +46,7 @@ get_native_window_size(SDL_Window *window) {
}
// get the windowed window size
static struct size
get_window_size(const struct screen *screen) {
static struct size get_window_size(const struct screen *screen) {
if (screen->fullscreen) {
return screen->windowed_window_size;
}
@@ -36,8 +54,7 @@ get_window_size(const struct screen *screen) {
}
// set the window size to be applied when fullscreen is disabled
static void
set_window_size(struct screen *screen, struct size new_size) {
static void set_window_size(struct screen *screen, struct size new_size) {
// setting the window size during fullscreen is implementation defined,
// so apply the resize only after fullscreen is disabled
if (screen->fullscreen) {
@@ -49,31 +66,28 @@ set_window_size(struct screen *screen, struct size new_size) {
}
// get the preferred display bounds (i.e. the screen bounds with some margins)
static bool
get_preferred_display_bounds(struct size *bounds) {
static SDL_bool get_preferred_display_bounds(struct size *bounds) {
SDL_Rect rect;
#ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
#if SDL_VERSION_ATLEAST(2, 0, 5)
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r))
#else
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayBounds((i), (r))
#endif
if (GET_DISPLAY_BOUNDS(0, &rect)) {
LOGW("Could not get display usable bounds: %s", SDL_GetError());
return false;
return SDL_FALSE;
}
bounds->width = MAX(0, rect.w - DISPLAY_MARGINS);
bounds->height = MAX(0, rect.h - DISPLAY_MARGINS);
return true;
return SDL_TRUE;
}
// return the optimal size of the window, with the following constraints:
// - it attempts to keep at least one dimension of the current_size (i.e. it
// crops the black borders)
// - it attempts to keep at least one dimension of the current_size (i.e. it crops the black borders)
// - it keeps the aspect ratio
// - it scales down to make it fit in the display_size
static struct size
get_optimal_size(struct size current_size, struct size frame_size) {
static struct size get_optimal_size(struct size current_size, struct size frame_size) {
if (frame_size.width == 0 || frame_size.height == 0) {
// avoid division by 0
return current_size;
@@ -81,8 +95,8 @@ get_optimal_size(struct size current_size, struct size frame_size) {
struct size display_size;
// 32 bits because we need to multiply two 16 bits values
uint32_t w;
uint32_t h;
Uint32 w;
Uint32 h;
if (!get_preferred_display_bounds(&display_size)) {
// cannot get display bounds, do not constraint the size
@@ -93,13 +107,12 @@ get_optimal_size(struct size current_size, struct size frame_size) {
h = MIN(current_size.height, display_size.height);
}
bool keep_width = frame_size.width * h > frame_size.height * w;
SDL_bool keep_width = frame_size.width * h > frame_size.height * w;
if (keep_width) {
// remove black borders on top and bottom
h = frame_size.height * w / frame_size.width;
} else {
// remove black borders on left and right (or none at all if it already
// fits)
// remove black borders on left and right (or none at all if it already fits)
w = frame_size.width * h / frame_size.height;
}
@@ -109,100 +122,89 @@ get_optimal_size(struct size current_size, struct size frame_size) {
}
// same as get_optimal_size(), but read the current size from the window
static inline struct size
get_optimal_window_size(const struct screen *screen, struct size frame_size) {
static inline struct size get_optimal_window_size(const struct screen *screen, struct size frame_size) {
struct size current_size = get_window_size(screen);
return get_optimal_size(current_size, frame_size);
}
// initially, there is no current size, so use the frame size as current size
static inline struct size
get_initial_optimal_size(struct size frame_size) {
static inline struct size get_initial_optimal_size(struct size frame_size) {
return get_optimal_size(frame_size, frame_size);
}
void
screen_init(struct screen *screen) {
// 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;
}
static inline SDL_Texture *
create_texture(SDL_Renderer *renderer, struct size frame_size) {
return SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING,
static inline SDL_Texture *create_texture(SDL_Renderer *renderer, struct size frame_size) {
return SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING,
frame_size.width, frame_size.height);
}
bool
screen_init_rendering(struct screen *screen, const char *device_name,
struct size frame_size, bool always_on_top) {
SDL_bool screen_init_rendering(struct screen *screen, const char *device_name, struct size frame_size) {
screen->frame_size = frame_size;
struct size window_size = get_initial_optimal_size(frame_size);
uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
Uint32 window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
#ifdef HIDPI_SUPPORT
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
#endif
if (always_on_top) {
#ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
#else
LOGW("The 'always on top' flag is not available "
"(compile with SDL >= 2.0.5 to enable it)");
#endif
}
screen->window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
window_size.width, window_size.height,
window_flags);
screen->window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
window_size.width, window_size.height, window_flags);
if (!screen->window) {
LOGC("Could not create window: %s", SDL_GetError());
return false;
return SDL_FALSE;
}
screen->renderer = SDL_CreateRenderer(screen->window, -1,
SDL_RENDERER_ACCELERATED);
screen->renderer = SDL_CreateRenderer(screen->window, -1, SDL_RENDERER_ACCELERATED);
if (!screen->renderer) {
LOGC("Could not create renderer: %s", SDL_GetError());
screen_destroy(screen);
return false;
return SDL_FALSE;
}
if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width,
frame_size.height)) {
if (render_set_scaled_logical_size(screen, frame_size)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
screen_destroy(screen);
return false;
return SDL_FALSE;
}
SDL_Surface *icon = read_xpm(icon_xpm);
if (icon) {
SDL_SetWindowIcon(screen->window, icon);
SDL_FreeSurface(icon);
} else {
LOGW("Could not load icon");
if (!icon) {
LOGE("Could not load icon: %s", SDL_GetError());
screen_destroy(screen);
return SDL_FALSE;
}
SDL_SetWindowIcon(screen->window, icon);
SDL_FreeSurface(icon);
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width,
frame_size.height);
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width, frame_size.height);
screen->texture = create_texture(screen->renderer, frame_size);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
screen_destroy(screen);
return false;
return SDL_FALSE;
}
return true;
return SDL_TRUE;
}
void
screen_show_window(struct screen *screen) {
void screen_show_window(struct screen *screen) {
SDL_ShowWindow(screen->window);
}
void
screen_destroy(struct screen *screen) {
void screen_destroy(struct screen *screen) {
if (screen->texture) {
SDL_DestroyTexture(screen->texture);
}
@@ -215,14 +217,11 @@ screen_destroy(struct screen *screen) {
}
// recreate the texture and resize the window if the frame size has changed
static 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 (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width,
new_frame_size.height)) {
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)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
return false;
return SDL_FALSE;
}
// frame dimension changed, destroy texture
@@ -230,67 +229,61 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) {
struct size current_size = get_window_size(screen);
struct size target_size = {
(uint32_t) current_size.width * new_frame_size.width
/ screen->frame_size.width,
(uint32_t) current_size.height * new_frame_size.height
/ screen->frame_size.height,
(Uint32) current_size.width * new_frame_size.width / screen->frame_size.width,
(Uint32) current_size.height * new_frame_size.height / screen->frame_size.height,
};
target_size = get_optimal_size(target_size, new_frame_size);
set_window_size(screen, target_size);
screen->frame_size = new_frame_size;
LOGI("New texture: %" PRIu16 "x%" PRIu16,
LOGD("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height);
screen->texture = create_texture(screen->renderer, new_frame_size);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
return false;
return SDL_FALSE;
}
}
return true;
return SDL_TRUE;
}
// write the frame into the texture
static void
update_texture(struct screen *screen, const AVFrame *frame) {
static void update_texture(struct screen *screen, const AVFrame *frame) {
SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]);
}
bool
screen_update_frame(struct screen *screen, struct video_buffer *vb) {
mutex_lock(vb->mutex);
const AVFrame *frame = video_buffer_consume_rendered_frame(vb);
SDL_bool screen_update_frame(struct screen *screen, struct frames *frames) {
mutex_lock(frames->mutex);
const AVFrame *frame = frames_consume_rendered_frame(frames);
struct size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) {
mutex_unlock(vb->mutex);
return false;
mutex_unlock(frames->mutex);
return SDL_FALSE;
}
update_texture(screen, frame);
mutex_unlock(vb->mutex);
mutex_unlock(frames->mutex);
screen_render(screen);
return true;
return SDL_TRUE;
}
void
screen_render(struct screen *screen) {
void screen_render(struct screen *screen) {
SDL_RenderClear(screen->renderer);
SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
SDL_RenderPresent(screen->renderer);
}
void
screen_switch_fullscreen(struct screen *screen) {
void screen_switch_fullscreen(struct screen *screen) {
if (!screen->fullscreen) {
// going to fullscreen, store the current windowed window size
screen->windowed_window_size = get_native_window_size(screen->window);
}
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
Uint32 new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
return;
@@ -299,30 +292,28 @@ screen_switch_fullscreen(struct screen *screen) {
screen->fullscreen = !screen->fullscreen;
if (!screen->fullscreen) {
// fullscreen disabled, restore expected windowed window size
SDL_SetWindowSize(screen->window, screen->windowed_window_size.width,
screen->windowed_window_size.height);
SDL_SetWindowSize(screen->window, screen->windowed_window_size.width, screen->windowed_window_size.height);
}
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
screen_render(screen);
}
void
screen_resize_to_fit(struct screen *screen) {
void screen_resize_to_fit(struct screen *screen) {
if (!screen->fullscreen) {
struct size optimal_size = get_optimal_window_size(screen,
screen->frame_size);
SDL_SetWindowSize(screen->window, optimal_size.width,
optimal_size.height);
struct size optimal_size = get_optimal_window_size(screen, screen->frame_size);
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
LOGD("Resized to optimal size");
}
}
void
screen_resize_to_pixel_perfect(struct screen *screen) {
void screen_resize_to_pixel_perfect(struct screen *screen) {
if (!screen->fullscreen) {
SDL_SetWindowSize(screen->window, screen->frame_size.width,
screen->frame_size.height);
SDL_SetWindowSize(screen->window, screen->frame_size.width, screen->frame_size.height);
LOGD("Resized to pixel-perfect");
}
}
void screen_update_scale(struct screen *screen) {
render_set_scaled_logical_size(screen, screen->frame_size);
}

View File

@@ -1,13 +1,11 @@
#ifndef SCREEN_H
#define SCREEN_H
#include <stdbool.h>
#include <SDL2/SDL.h>
#include <libavformat/avformat.h>
#include "common.h"
struct video_buffer;
#include "frames.h"
struct screen {
SDL_Window *window;
@@ -16,9 +14,8 @@ struct screen {
struct size frame_size;
//used only in fullscreen mode to know the windowed window size
struct size windowed_window_size;
bool has_frame;
bool fullscreen;
bool no_window;
SDL_bool has_frame;
SDL_bool fullscreen;
};
#define SCREEN_INITIALIZER { \
@@ -33,46 +30,44 @@ struct screen {
.width = 0, \
.height = 0, \
}, \
.has_frame = false, \
.fullscreen = false, \
.no_window = false, \
.has_frame = SDL_FALSE, \
.fullscreen = SDL_FALSE, \
}
// init SDL and set appropriate hints
SDL_bool sdl_init_and_configure(void);
// initialize default values
void
screen_init(struct screen *screen);
void screen_init(struct screen *screen);
// initialize screen, create window, renderer and texture (window is hidden)
bool
screen_init_rendering(struct screen *screen, const char *device_name,
struct size frame_size, bool always_on_top);
SDL_bool screen_init_rendering(struct screen *screen,
const char *device_name,
struct size frame_size);
// show the window
void
screen_show_window(struct screen *screen);
void screen_show_window(struct screen *screen);
// destroy window, renderer and texture (if any)
void
screen_destroy(struct screen *screen);
void screen_destroy(struct screen *screen);
// resize if necessary and write the rendered frame into the texture
bool
screen_update_frame(struct screen *screen, struct video_buffer *vb);
SDL_bool screen_update_frame(struct screen *screen, struct frames *frames);
// render the texture to the renderer
void
screen_render(struct screen *screen);
void screen_render(struct screen *screen);
// switch the fullscreen mode
void
screen_switch_fullscreen(struct screen *screen);
void screen_switch_fullscreen(struct screen *screen);
// resize window to optimal size (remove black borders)
void
screen_resize_to_fit(struct screen *screen);
void screen_resize_to_fit(struct screen *screen);
// resize window to 1:1 (pixel-perfect)
void
screen_resize_to_pixel_perfect(struct screen *screen);
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

@@ -2,6 +2,7 @@
#include <errno.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_timer.h>
@@ -20,8 +21,7 @@
#define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
static const char *
get_server_path(void) {
static const char *get_server_path(void) {
const char *server_path = getenv("SCRCPY_SERVER_PATH");
if (!server_path) {
server_path = DEFAULT_SERVER_PATH;
@@ -29,60 +29,56 @@ get_server_path(void) {
return server_path;
}
static bool
push_server(const char *serial) {
static SDL_bool push_server(const char *serial) {
process_t process = adb_push(serial, get_server_path(), DEVICE_SERVER_PATH);
return process_check_success(process, "adb push");
}
static bool
enable_tunnel_reverse(const char *serial, uint16_t local_port) {
static SDL_bool remove_server(const char *serial) {
process_t process = adb_remove_path(serial, DEVICE_SERVER_PATH);
return process_check_success(process, "adb shell rm");
}
static SDL_bool enable_tunnel_reverse(const char *serial, Uint16 local_port) {
process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
return process_check_success(process, "adb reverse");
}
static bool
disable_tunnel_reverse(const char *serial) {
static SDL_bool disable_tunnel_reverse(const char *serial) {
process_t process = adb_reverse_remove(serial, SOCKET_NAME);
return process_check_success(process, "adb reverse --remove");
}
static bool
enable_tunnel_forward(const char *serial, uint16_t local_port) {
static SDL_bool enable_tunnel_forward(const char *serial, Uint16 local_port) {
process_t process = adb_forward(serial, local_port, SOCKET_NAME);
return process_check_success(process, "adb forward");
}
static bool
disable_tunnel_forward(const char *serial, uint16_t local_port) {
static SDL_bool disable_tunnel_forward(const char *serial, Uint16 local_port) {
process_t process = adb_forward_remove(serial, local_port);
return process_check_success(process, "adb forward --remove");
}
static bool
enable_tunnel(struct server *server) {
static SDL_bool enable_tunnel(struct server *server) {
if (enable_tunnel_reverse(server->serial, server->local_port)) {
return true;
return SDL_TRUE;
}
LOGW("'adb reverse' failed, fallback to 'adb forward'");
server->tunnel_forward = true;
server->tunnel_forward = SDL_TRUE;
return enable_tunnel_forward(server->serial, server->local_port);
}
static bool
disable_tunnel(struct server *server) {
static SDL_bool disable_tunnel(struct server *server) {
if (server->tunnel_forward) {
return disable_tunnel_forward(server->serial, server->local_port);
}
return disable_tunnel_reverse(server->serial);
}
static process_t
execute_server(const char *serial,
uint16_t max_size, uint32_t bit_rate,
bool tunnel_forward, const char *crop,
bool send_frame_meta) {
static process_t execute_server(const char *serial,
Uint16 max_size, Uint32 bit_rate,
const char *crop, SDL_bool tunnel_forward) {
char max_size_string[6];
char bit_rate_string[11];
sprintf(max_size_string, "%"PRIu16, max_size);
@@ -96,21 +92,18 @@ execute_server(const char *serial,
max_size_string,
bit_rate_string,
tunnel_forward ? "true" : "false",
crop ? crop : "-",
send_frame_meta ? "true" : "false",
crop ? crop : "",
};
return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
}
#define IPV4_LOCALHOST 0x7F000001
static socket_t
listen_on_port(uint16_t port) {
static socket_t listen_on_port(Uint16 port) {
return net_listen(IPV4_LOCALHOST, port, 1);
}
static socket_t
connect_and_read_byte(uint16_t port) {
static socket_t connect_and_read_byte(Uint16 port) {
socket_t socket = net_connect(IPV4_LOCALHOST, port);
if (socket == INVALID_SOCKET) {
return INVALID_SOCKET;
@@ -119,15 +112,14 @@ connect_and_read_byte(uint16_t port) {
char byte;
// the connection may succeed even if the server behind the "adb tunnel"
// is not listening, so read one byte to detect a working connection
if (net_recv(socket, &byte, 1) != 1) {
if (net_recv_all(socket, &byte, 1) != 1) {
// the server is not listening yet behind the adb tunnel
return INVALID_SOCKET;
}
return socket;
}
static socket_t
connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
static socket_t connect_to_server(Uint16 port, Uint32 attempts, Uint32 delay) {
do {
LOGD("Remaining connection attempts: %d", (int) attempts);
socket_t socket = connect_and_read_byte(port);
@@ -142,8 +134,7 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
return INVALID_SOCKET;
}
static void
close_socket(socket_t *socket) {
static void close_socket(socket_t *socket) {
SDL_assert(*socket != INVALID_SOCKET);
net_shutdown(*socket, SHUT_RDWR);
if (!net_close(*socket)) {
@@ -153,32 +144,31 @@ close_socket(socket_t *socket) {
*socket = INVALID_SOCKET;
}
void
server_init(struct server *server) {
void server_init(struct server *server) {
*server = (struct server) SERVER_INITIALIZER;
}
bool
server_start(struct server *server, const char *serial,
uint16_t local_port, uint16_t max_size, uint32_t bit_rate,
const char *crop, bool send_frame_meta) {
SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port,
Uint16 max_size, Uint32 bit_rate, const char *crop) {
server->local_port = local_port;
if (serial) {
server->serial = SDL_strdup(serial);
if (!server->serial) {
return false;
return SDL_FALSE;
}
}
if (!push_server(serial)) {
SDL_free(server->serial);
return false;
SDL_free((void *) server->serial);
return SDL_FALSE;
}
server->server_copied_to_device = SDL_TRUE;
if (!enable_tunnel(server)) {
SDL_free(server->serial);
return false;
SDL_free((void *) server->serial);
return SDL_FALSE;
}
// if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to
@@ -187,80 +177,65 @@ server_start(struct server *server, const char *serial,
// At the application level, the device part is "the server" because it
// serves video stream and control. However, at the network level, the
// client listens and the server connects to the client. That way, the
// client can listen before starting the server app, so there is no
// need to try to connect until the server socket is listening on the
// device.
// client can listen before starting the server app, so there is no need to
// try to connect until the server socket is listening on the device.
server->server_socket = listen_on_port(local_port);
if (server->server_socket == INVALID_SOCKET) {
LOGE("Could not listen on port %" PRIu16, local_port);
disable_tunnel(server);
SDL_free(server->serial);
return false;
SDL_free((void *) server->serial);
return SDL_FALSE;
}
}
// server will connect to our server socket
server->process = execute_server(serial, max_size, bit_rate,
server->tunnel_forward, crop,
send_frame_meta);
server->process = execute_server(serial, max_size, bit_rate, crop,
server->tunnel_forward);
if (server->process == PROCESS_NONE) {
if (!server->tunnel_forward) {
close_socket(&server->server_socket);
}
disable_tunnel(server);
SDL_free(server->serial);
return false;
SDL_free((void *) server->serial);
return SDL_FALSE;
}
server->tunnel_enabled = true;
server->tunnel_enabled = SDL_TRUE;
return true;
return SDL_TRUE;
}
bool
server_connect_to(struct server *server) {
socket_t server_connect_to(struct server *server) {
if (!server->tunnel_forward) {
server->video_socket = net_accept(server->server_socket);
if (server->video_socket == INVALID_SOCKET) {
return false;
}
server->device_socket = net_accept(server->server_socket);
} else {
Uint32 attempts = 100;
Uint32 delay = 100; // ms
server->device_socket = connect_to_server(server->local_port, attempts, delay);
}
server->control_socket = net_accept(server->server_socket);
if (server->control_socket == INVALID_SOCKET) {
// the video_socket will be clean up on destroy
return false;
}
if (server->device_socket == INVALID_SOCKET) {
return INVALID_SOCKET;
}
if (!server->tunnel_forward) {
// we don't need the server socket anymore
close_socket(&server->server_socket);
} else {
uint32_t attempts = 100;
uint32_t delay = 100; // ms
server->video_socket =
connect_to_server(server->local_port, attempts, delay);
if (server->video_socket == INVALID_SOCKET) {
return false;
}
// we know that the device is listening, we don't need several attempts
server->control_socket =
net_connect(IPV4_LOCALHOST, server->local_port);
if (server->control_socket == INVALID_SOCKET) {
return false;
}
}
// the server is started, we can clean up the jar from the temporary folder
remove_server(server->serial); // ignore failure
server->server_copied_to_device = SDL_FALSE;
// we don't need the adb tunnel anymore
disable_tunnel(server); // ignore failure
server->tunnel_enabled = false;
server->tunnel_enabled = SDL_FALSE;
return true;
return server->device_socket;
}
void
server_stop(struct server *server) {
void server_stop(struct server *server) {
SDL_assert(server->process != PROCESS_NONE);
if (!cmd_terminate(server->process)) {
@@ -274,18 +249,18 @@ server_stop(struct server *server) {
// ignore failure
disable_tunnel(server);
}
if (server->server_copied_to_device) {
remove_server(server->serial); // ignore failure
}
}
void
server_destroy(struct server *server) {
void server_destroy(struct server *server) {
if (server->server_socket != INVALID_SOCKET) {
close_socket(&server->server_socket);
}
if (server->video_socket != INVALID_SOCKET) {
close_socket(&server->video_socket);
if (server->device_socket != INVALID_SOCKET) {
close_socket(&server->device_socket);
}
if (server->control_socket != INVALID_SOCKET) {
close_socket(&server->control_socket);
}
SDL_free(server->serial);
SDL_free((void *) server->serial);
}

View File

@@ -1,56 +1,45 @@
#ifndef SERVER_H
#define SERVER_H
#include <stdbool.h>
#include <stdint.h>
#include "command.h"
#include "net.h"
struct server {
char *serial;
const char *serial;
process_t process;
socket_t server_socket; // only used if !tunnel_forward
socket_t video_socket;
socket_t control_socket;
uint16_t local_port;
bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
bool send_frame_meta; // request frame PTS to be able to record properly
socket_t device_socket;
Uint16 local_port;
SDL_bool tunnel_enabled;
SDL_bool tunnel_forward; // use "adb forward" instead of "adb reverse"
SDL_bool server_copied_to_device;
};
#define SERVER_INITIALIZER { \
.serial = NULL, \
.process = PROCESS_NONE, \
.server_socket = INVALID_SOCKET, \
.video_socket = INVALID_SOCKET, \
.control_socket = INVALID_SOCKET, \
.local_port = 0, \
.tunnel_enabled = false, \
.tunnel_forward = false, \
.send_frame_meta = false, \
#define SERVER_INITIALIZER { \
.serial = NULL, \
.process = PROCESS_NONE, \
.server_socket = INVALID_SOCKET, \
.device_socket = INVALID_SOCKET, \
.local_port = 0, \
.tunnel_enabled = SDL_FALSE, \
.tunnel_forward = SDL_FALSE, \
.server_copied_to_device = SDL_FALSE, \
}
// init default values
void
server_init(struct server *server);
void server_init(struct server *server);
// push, enable tunnel et start the server
bool
server_start(struct server *server, const char *serial,
uint16_t local_port, uint16_t max_size, uint32_t bit_rate,
const char *crop, bool send_frame_meta);
SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port,
Uint16 max_size, Uint32 bit_rate, const char *crop);
// block until the communication with the server is established
bool
server_connect_to(struct server *server);
socket_t server_connect_to(struct server *server);
// disconnect and kill the server process
void
server_stop(struct server *server);
void server_stop(struct server *server);
// close and release sockets
void
server_destroy(struct server *server);
void server_destroy(struct server *server);
#endif

View File

@@ -1,15 +1,6 @@
#include "str_util.h"
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
# include <windows.h>
# include <tchar.h>
#endif
size_t
xstrncpy(char *dest, const char *src, size_t n) {
size_t xstrncpy(char *dest, const char *src, size_t n) {
size_t i;
for (i = 0; i < n - 1 && src[i] != '\0'; ++i)
dest[i] = src[i];
@@ -18,8 +9,7 @@ xstrncpy(char *dest, const char *src, size_t n) {
return src[i] == '\0' ? i : n;
}
size_t
xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) {
size_t xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) {
const char *const *remaining = tokens;
const char *token = *remaining++;
size_t i = 0;
@@ -41,37 +31,3 @@ truncated:
dst[n - 1] = '\0';
return n;
}
char *
strquote(const char *src) {
size_t len = strlen(src);
char *quoted = malloc(len + 3);
if (!quoted) {
return NULL;
}
memcpy(&quoted[1], src, len);
quoted[0] = '"';
quoted[len + 1] = '"';
quoted[len + 2] = '\0';
return quoted;
}
#ifdef _WIN32
wchar_t *
utf8_to_wide_char(const char *utf8) {
int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
if (!len) {
return NULL;
}
wchar_t *wide = malloc(len * sizeof(wchar_t));
if (!wide) {
return NULL;
}
MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wide, len);
return wide;
}
#endif

View File

@@ -9,25 +9,11 @@
// - it does not write useless bytes if strlen(src) < n
// - it returns the number of chars actually written (max n-1) if src has
// been copied completely, or n if src has been truncated
size_t
xstrncpy(char *dest, const char *src, size_t n);
size_t xstrncpy(char *dest, const char *src, size_t n);
// join tokens by sep into dst
// returns the number of chars actually written (max n-1) if no trucation
// occurred, or n if truncated
size_t
xstrjoin(char *dst, const char *const tokens[], char sep, size_t n);
// quote a string
// returns the new allocated string, to be freed by the caller
char *
strquote(const char *src);
#ifdef _WIN32
// convert a UTF-8 string to a wchar_t string
// returns the new allocated string, to be freed by the caller
wchar_t *
utf8_to_wide_char(const char *utf8);
#endif
size_t xstrjoin(char *dst, const char *const tokens[], char sep, size_t n);
#endif

View File

@@ -1,286 +0,0 @@
#include "stream.h"
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include <unistd.h>
#include "compat.h"
#include "config.h"
#include "buffer_util.h"
#include "decoder.h"
#include "events.h"
#include "lock_util.h"
#include "log.h"
#include "recorder.h"
#define BUFSIZE 0x10000
#define HEADER_SIZE 12
#define NO_PTS UINT64_C(-1)
static struct frame_meta *
frame_meta_new(uint64_t pts) {
struct frame_meta *meta = malloc(sizeof(*meta));
if (!meta) {
return meta;
}
meta->pts = pts;
meta->next = NULL;
return meta;
}
static void
frame_meta_delete(struct frame_meta *frame_meta) {
free(frame_meta);
}
static bool
receiver_state_push_meta(struct receiver_state *state, uint64_t pts) {
struct frame_meta *frame_meta = frame_meta_new(pts);
if (!frame_meta) {
return false;
}
// append to the list
// (iterate to find the last item, in practice the list should be tiny)
struct frame_meta **p = &state->frame_meta_queue;
while (*p) {
p = &(*p)->next;
}
*p = frame_meta;
return true;
}
static uint64_t
receiver_state_take_meta(struct receiver_state *state) {
struct frame_meta *frame_meta = state->frame_meta_queue; // first item
SDL_assert(frame_meta); // must not be empty
uint64_t pts = frame_meta->pts;
state->frame_meta_queue = frame_meta->next; // remove the item
frame_meta_delete(frame_meta);
return pts;
}
static int
read_packet_with_meta(void *opaque, uint8_t *buf, int buf_size) {
struct stream *stream = opaque;
struct receiver_state *state = &stream->receiver_state;
// The video stream contains raw packets, without time information. When we
// record, we retrieve the timestamps separately, from a "meta" header
// added by the server before each raw packet.
//
// The "meta" header length is 12 bytes:
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
// <-------------> <-----> <-----------------------------...
// PTS packet raw packet
// size
//
// It is followed by <packet_size> bytes containing the packet/frame.
if (!state->remaining) {
#define HEADER_SIZE 12
uint8_t header[HEADER_SIZE];
ssize_t r = net_recv_all(stream->socket, header, HEADER_SIZE);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
// no partial read (net_recv_all())
SDL_assert_release(r == HEADER_SIZE);
uint64_t pts = buffer_read64be(header);
state->remaining = buffer_read32be(&header[8]);
if (pts != NO_PTS && !receiver_state_push_meta(state, pts)) {
LOGE("Could not store PTS for recording");
// we cannot save the PTS, the recording would be broken
return AVERROR(ENOMEM);
}
}
SDL_assert(state->remaining);
if (buf_size > state->remaining) {
buf_size = state->remaining;
}
ssize_t r = net_recv(stream->socket, buf, buf_size);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
SDL_assert(state->remaining >= r);
state->remaining -= r;
return r;
}
static int
read_raw_packet(void *opaque, uint8_t *buf, int buf_size) {
struct stream *stream = opaque;
ssize_t r = net_recv(stream->socket, buf, buf_size);
if (r == -1) {
return AVERROR(errno);
}
if (r == 0) {
return AVERROR_EOF;
}
return r;
}
static void
notify_stopped(void) {
SDL_Event stop_event;
stop_event.type = EVENT_STREAM_STOPPED;
SDL_PushEvent(&stop_event);
}
static int
run_stream(void *data) {
struct stream *stream = data;
AVFormatContext *format_ctx = avformat_alloc_context();
if (!format_ctx) {
LOGC("Could not allocate format context");
goto end;
}
unsigned char *buffer = av_malloc(BUFSIZE);
if (!buffer) {
LOGC("Could not allocate buffer");
goto finally_free_format_ctx;
}
// initialize the receiver state
stream->receiver_state.frame_meta_queue = NULL;
stream->receiver_state.remaining = 0;
// if recording is enabled, a "header" is sent between raw packets
int (*read_packet)(void *, uint8_t *, int) =
stream->recorder ? read_packet_with_meta : read_raw_packet;
AVIOContext *avio_ctx = avio_alloc_context(buffer, BUFSIZE, 0, stream,
read_packet, NULL, NULL);
if (!avio_ctx) {
LOGC("Could not allocate avio context");
// avformat_open_input takes ownership of 'buffer'
// so only free the buffer before avformat_open_input()
av_free(buffer);
goto finally_free_format_ctx;
}
format_ctx->pb = avio_ctx;
if (avformat_open_input(&format_ctx, NULL, NULL, NULL) < 0) {
LOGE("Could not open video stream");
goto finally_free_avio_ctx;
}
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec) {
LOGE("H.264 decoder not found");
goto end;
}
if (stream->decoder && !decoder_open(stream->decoder, codec)) {
LOGE("Could not open decoder");
goto finally_close_input;
}
if (stream->recorder && !recorder_open(stream->recorder, codec)) {
LOGE("Could not open recorder");
goto finally_close_input;
}
AVPacket packet;
av_init_packet(&packet);
packet.data = NULL;
packet.size = 0;
while (!av_read_frame(format_ctx, &packet)) {
if (stream->decoder && !decoder_push(stream->decoder, &packet)) {
av_packet_unref(&packet);
goto quit;
}
if (stream->recorder) {
// we retrieve the PTS in order they were received, so they will
// be assigned to the correct frame
uint64_t pts = receiver_state_take_meta(&stream->receiver_state);
packet.pts = pts;
packet.dts = pts;
// no need to rescale with av_packet_rescale_ts(), the timestamps
// are in microseconds both in input and output
if (!recorder_write(stream->recorder, &packet)) {
LOGE("Could not write frame to output file");
av_packet_unref(&packet);
goto quit;
}
}
av_packet_unref(&packet);
if (avio_ctx->eof_reached) {
break;
}
}
LOGD("End of frames");
quit:
if (stream->recorder) {
recorder_close(stream->recorder);
}
finally_close_input:
avformat_close_input(&format_ctx);
finally_free_avio_ctx:
av_free(avio_ctx->buffer);
av_free(avio_ctx);
finally_free_format_ctx:
avformat_free_context(format_ctx);
end:
notify_stopped();
return 0;
}
void
stream_init(struct stream *stream, socket_t socket,
struct decoder *decoder, struct recorder *recorder) {
stream->socket = socket;
stream->decoder = decoder,
stream->recorder = recorder;
}
bool
stream_start(struct stream *stream) {
LOGD("Starting stream thread");
stream->thread = SDL_CreateThread(run_stream, "stream", stream);
if (!stream->thread) {
LOGC("Could not start stream thread");
return false;
}
return true;
}
void
stream_stop(struct stream *stream) {
if (stream->decoder) {
decoder_interrupt(stream->decoder);
}
}
void
stream_join(struct stream *stream) {
SDL_WaitThread(stream->thread, NULL);
}

View File

@@ -1,43 +0,0 @@
#ifndef STREAM_H
#define STREAM_H
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_thread.h>
#include "net.h"
struct video_buffer;
struct frame_meta {
uint64_t pts;
struct frame_meta *next;
};
struct stream {
socket_t socket;
struct video_buffer *video_buffer;
SDL_Thread *thread;
struct decoder *decoder;
struct recorder *recorder;
struct receiver_state {
// meta (in order) for frames not consumed yet
struct frame_meta *frame_meta_queue;
size_t remaining; // remaining bytes to receive for the current frame
} receiver_state;
};
void
stream_init(struct stream *stream, socket_t socket,
struct decoder *decoder, struct recorder *recorder);
bool
stream_start(struct stream *stream);
void
stream_stop(struct stream *stream);
void
stream_join(struct stream *stream);
#endif

View File

@@ -1,5 +1,3 @@
#define _POSIX_SOURCE // for kill()
#include "command.h"
#include <errno.h>
@@ -11,8 +9,7 @@
#include <unistd.h>
#include "log.h"
enum process_result
cmd_execute(const char *path, const char *const argv[], pid_t *pid) {
enum process_result cmd_execute(const char *path, const char *const argv[], pid_t *pid) {
int fd[2];
if (pipe(fd) == -1) {
@@ -73,18 +70,15 @@ end:
return ret;
}
bool
cmd_terminate(pid_t 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);
LOGC("Requested to kill %d, this is an error. Please report the bug.\n", (int) pid);
abort();
}
return kill(pid, SIGTERM) != -1;
}
bool
cmd_simple_wait(pid_t pid, int *exit_code) {
SDL_bool cmd_simple_wait(pid_t pid, int *exit_code) {
int status;
int code;
if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) {

View File

@@ -1,19 +1,16 @@
#include "net.h"
#include <unistd.h>
# include <unistd.h>
bool
net_init(void) {
SDL_bool net_init(void) {
// do nothing
return true;
return SDL_TRUE;
}
void
net_cleanup(void) {
void net_cleanup(void) {
// do nothing
}
bool
net_close(socket_t socket) {
SDL_bool net_close(socket_t socket) {
return !close(socket);
}

View File

@@ -4,47 +4,30 @@
#include "log.h"
#include "str_util.h"
static int
build_cmd(char *cmd, size_t len, const char *const argv[]) {
// Windows command-line parsing is WTF:
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
// only make it work for this very specific program
// (don't handle escaping nor quotes)
size_t ret = xstrjoin(cmd, argv, ' ', len);
if (ret >= len) {
LOGE("Command too long (%" PRIsizet " chars)", len - 1);
return -1;
}
return 0;
}
enum process_result
cmd_execute(const char *path, const char *const argv[], HANDLE *handle) {
STARTUPINFOW si;
enum process_result cmd_execute(const char *path, const char *const argv[], HANDLE *handle) {
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];
if (build_cmd(cmd, sizeof(cmd), argv)) {
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;
}
wchar_t *wide = utf8_to_wide_char(cmd);
if (!wide) {
LOGC("Cannot allocate wide char string");
return PROCESS_ERROR_GENERIC;
}
#ifdef WINDOWS_NOCONSOLE
int flags = CREATE_NO_WINDOW;
#else
int flags = 0;
#endif
if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, flags, NULL, NULL, &si,
&pi)) {
free(wide);
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;
@@ -52,21 +35,17 @@ cmd_execute(const char *path, const char *const argv[], HANDLE *handle) {
return PROCESS_ERROR_GENERIC;
}
free(wide);
*handle = pi.hProcess;
return PROCESS_SUCCESS;
}
bool
cmd_terminate(HANDLE handle) {
SDL_bool cmd_terminate(HANDLE handle) {
return TerminateProcess(handle, 1) && CloseHandle(handle);
}
bool
cmd_simple_wait(HANDLE handle, DWORD *exit_code) {
SDL_bool cmd_simple_wait(HANDLE handle, DWORD *exit_code) {
DWORD code;
if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0
|| !GetExitCodeProcess(handle, &code)) {
if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 || !GetExitCodeProcess(handle, &code)) {
// cannot wait or retrieve the exit code
code = -1; // max value, it's unsigned
}

View File

@@ -2,23 +2,20 @@
#include "log.h"
bool
net_init(void) {
SDL_bool net_init(void) {
WSADATA wsa;
int res = WSAStartup(MAKEWORD(2, 2), &wsa) < 0;
if (res < 0) {
LOGC("WSAStartup failed with error %d", res);
return false;
return SDL_FALSE;
}
return true;
return SDL_TRUE;
}
void
net_cleanup(void) {
void net_cleanup(void) {
WSACleanup();
}
bool
net_close(socket_t socket) {
SDL_bool net_close(socket_t socket) {
return !closesocket(socket);
}

View File

@@ -1,7 +1,5 @@
#include "tiny_xpm.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -9,20 +7,19 @@
struct index {
char c;
uint32_t color;
Uint32 color;
};
static bool
find_color(struct index *index, int len, char c, uint32_t *color) {
static SDL_bool find_color(struct index *index, int len, char c, Uint32 *color) {
// there are typically very few color, so it's ok to iterate over the array
for (int i = 0; i < len; ++i) {
if (index[i].c == c) {
*color = index[i].color;
return true;
return SDL_TRUE;
}
}
*color = 0;
return false;
return SDL_FALSE;
}
// We encounter some problems with SDL2_image on MSYS2 (Windows),
@@ -33,8 +30,7 @@ find_color(struct index *index, int len, char c, uint32_t *color) {
//
// Parameter is not "const char *" because XPM formats are generally stored in a
// (non-const) "char *"
SDL_Surface *
read_xpm(char *xpm[]) {
SDL_Surface *read_xpm(char *xpm[]) {
#if SDL_ASSERT_LEVEL >= 2
// patch the XPM to change the icon color in debug mode
xpm[2] = ". c #CC00CC";
@@ -73,7 +69,7 @@ read_xpm(char *xpm[]) {
}
// parse image
uint32_t *pixels = SDL_malloc(4 * width * height);
Uint32 *pixels = SDL_malloc(4 * width * height);
if (!pixels) {
LOGE("Could not allocate icon memory");
return NULL;
@@ -82,33 +78,29 @@ read_xpm(char *xpm[]) {
const char *line = xpm[1 + colors + y];
for (int x = 0; x < width; ++x) {
char c = line[x];
uint32_t color;
bool color_found = find_color(index, colors, c, &color);
Uint32 color;
SDL_bool color_found = find_color(index, colors, c, &color);
SDL_assert(color_found);
pixels[y * width + x] = color;
}
}
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
uint32_t amask = 0x000000ff;
uint32_t rmask = 0x0000ff00;
uint32_t gmask = 0x00ff0000;
uint32_t bmask = 0xff000000;
Uint32 amask = 0x000000ff;
Uint32 rmask = 0x0000ff00;
Uint32 gmask = 0x00ff0000;
Uint32 bmask = 0xff000000;
#else // little endian, like x86
uint32_t amask = 0xff000000;
uint32_t rmask = 0x00ff0000;
uint32_t gmask = 0x0000ff00;
uint32_t bmask = 0x000000ff;
Uint32 amask = 0xff000000;
Uint32 rmask = 0x00ff0000;
Uint32 gmask = 0x0000ff00;
Uint32 bmask = 0x000000ff;
#endif
SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(pixels,
width, height,
32, 4 * width,
rmask, gmask, bmask, amask);
if (!surface) {
LOGE("Could not create icon surface");
return NULL;
}
// make the surface own the raw pixels
surface->flags &= ~SDL_PREALLOC;
return surface;

View File

@@ -3,7 +3,6 @@
#include <SDL2/SDL.h>
SDL_Surface *
read_xpm(char *xpm[]);
SDL_Surface *read_xpm(char *xpm[]);
#endif

View File

@@ -1,116 +0,0 @@
#include "video_buffer.h"
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_mutex.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include "config.h"
#include "lock_util.h"
#include "log.h"
bool
video_buffer_init(struct video_buffer *vb) {
if (!(vb->decoding_frame = av_frame_alloc())) {
goto error_0;
}
if (!(vb->rendering_frame = av_frame_alloc())) {
goto error_1;
}
if (!(vb->mutex = SDL_CreateMutex())) {
goto error_2;
}
#ifndef SKIP_FRAMES
if (!(vb->rendering_frame_consumed_cond = SDL_CreateCond())) {
SDL_DestroyMutex(vb->mutex);
goto error_2;
}
vb->interrupted = false;
#endif
// there is initially no rendering frame, so consider it has already been
// consumed
vb->rendering_frame_consumed = true;
fps_counter_init(&vb->fps_counter);
return true;
error_2:
av_frame_free(&vb->rendering_frame);
error_1:
av_frame_free(&vb->decoding_frame);
error_0:
return false;
}
void
video_buffer_destroy(struct video_buffer *vb) {
#ifndef SKIP_FRAMES
SDL_DestroyCond(vb->rendering_frame_consumed_cond);
#endif
SDL_DestroyMutex(vb->mutex);
av_frame_free(&vb->rendering_frame);
av_frame_free(&vb->decoding_frame);
}
static void
video_buffer_swap_frames(struct video_buffer *vb) {
AVFrame *tmp = vb->decoding_frame;
vb->decoding_frame = vb->rendering_frame;
vb->rendering_frame = tmp;
}
void
video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped) {
mutex_lock(vb->mutex);
#ifndef SKIP_FRAMES
// if SKIP_FRAMES is disabled, then the decoder must wait for the current
// frame to be consumed
while (!vb->rendering_frame_consumed && !vb->interrupted) {
cond_wait(vb->rendering_frame_consumed_cond, vb->mutex);
}
#else
if (vb->fps_counter.started && !vb->rendering_frame_consumed) {
fps_counter_add_skipped_frame(&vb->fps_counter);
}
#endif
video_buffer_swap_frames(vb);
*previous_frame_skipped = !vb->rendering_frame_consumed;
vb->rendering_frame_consumed = false;
mutex_unlock(vb->mutex);
}
const AVFrame *
video_buffer_consume_rendered_frame(struct video_buffer *vb) {
SDL_assert(!vb->rendering_frame_consumed);
vb->rendering_frame_consumed = true;
if (vb->fps_counter.started) {
fps_counter_add_rendered_frame(&vb->fps_counter);
}
#ifndef SKIP_FRAMES
// if SKIP_FRAMES is disabled, then notify the decoder the current frame is
// consumed, so that it may push a new one
cond_signal(vb->rendering_frame_consumed_cond);
#endif
return vb->rendering_frame;
}
void
video_buffer_interrupt(struct video_buffer *vb) {
#ifdef SKIP_FRAMES
(void) vb; // unused
#else
mutex_lock(vb->mutex);
vb->interrupted = true;
mutex_unlock(vb->mutex);
// wake up blocking wait
cond_signal(vb->rendering_frame_consumed_cond);
#endif
}

View File

@@ -3,31 +3,31 @@
#include "control_event.h"
static void test_control_event_queue_empty(void) {
static void test_control_event_queue_empty() {
struct control_event_queue queue;
bool init_ok = control_event_queue_init(&queue);
SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
assert(control_event_queue_is_empty(&queue));
struct control_event dummy_event;
bool push_ok = control_event_queue_push(&queue, &dummy_event);
SDL_bool push_ok = control_event_queue_push(&queue, &dummy_event);
assert(push_ok);
assert(!control_event_queue_is_empty(&queue));
bool take_ok = control_event_queue_take(&queue, &dummy_event);
SDL_bool take_ok = control_event_queue_take(&queue, &dummy_event);
assert(take_ok);
assert(control_event_queue_is_empty(&queue));
bool take_empty_ok = control_event_queue_take(&queue, &dummy_event);
SDL_bool take_empty_ok = control_event_queue_take(&queue, &dummy_event);
assert(!take_empty_ok); // the queue is empty
control_event_queue_destroy(&queue);
}
static void test_control_event_queue_full(void) {
static void test_control_event_queue_full() {
struct control_event_queue queue;
bool init_ok = control_event_queue_init(&queue);
SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
assert(!control_event_queue_is_full(&queue));
@@ -36,16 +36,16 @@ static void test_control_event_queue_full(void) {
// fill the queue
while (control_event_queue_push(&queue, &dummy_event));
bool take_ok = control_event_queue_take(&queue, &dummy_event);
SDL_bool take_ok = control_event_queue_take(&queue, &dummy_event);
assert(take_ok);
assert(!control_event_queue_is_full(&queue));
control_event_queue_destroy(&queue);
}
static void test_control_event_queue_push_take(void) {
static void test_control_event_queue_push_take() {
struct control_event_queue queue;
bool init_ok = control_event_queue_init(&queue);
SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
struct control_event event = {
@@ -57,7 +57,7 @@ static void test_control_event_queue_push_take(void) {
},
};
bool push1_ok = control_event_queue_push(&queue, &event);
SDL_bool push1_ok = control_event_queue_push(&queue, &event);
assert(push1_ok);
event = (struct control_event) {
@@ -67,11 +67,11 @@ static void test_control_event_queue_push_take(void) {
},
};
bool push2_ok = control_event_queue_push(&queue, &event);
SDL_bool push2_ok = control_event_queue_push(&queue, &event);
assert(push2_ok);
// overwrite event
bool take1_ok = control_event_queue_take(&queue, &event);
SDL_bool take1_ok = control_event_queue_take(&queue, &event);
assert(take1_ok);
assert(event.type == CONTROL_EVENT_TYPE_KEYCODE);
assert(event.keycode_event.action == AKEY_EVENT_ACTION_DOWN);
@@ -79,7 +79,7 @@ static void test_control_event_queue_push_take(void) {
assert(event.keycode_event.metastate == (AMETA_CTRL_LEFT_ON | AMETA_CTRL_ON));
// overwrite event
bool take2_ok = control_event_queue_take(&queue, &event);
SDL_bool take2_ok = control_event_queue_take(&queue, &event);
assert(take2_ok);
assert(event.type == CONTROL_EVENT_TYPE_TEXT);
assert(!strcmp(event.text_event.text, "abc"));
@@ -87,7 +87,7 @@ static void test_control_event_queue_push_take(void) {
control_event_queue_destroy(&queue);
}
int main(void) {
int main() {
test_control_event_queue_empty();
test_control_event_queue_full();
test_control_event_queue_push_take();

View File

@@ -3,7 +3,7 @@
#include "control_event.h"
static void test_serialize_keycode_event(void) {
static void test_serialize_keycode_event() {
struct control_event event = {
.type = CONTROL_EVENT_TYPE_KEYCODE,
.keycode_event = {
@@ -26,7 +26,7 @@ static void test_serialize_keycode_event(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_text_event(void) {
static void test_serialize_text_event() {
struct control_event event = {
.type = CONTROL_EVENT_TYPE_TEXT,
.text_event = {
@@ -46,17 +46,16 @@ static void test_serialize_text_event(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_long_text_event(void) {
static void test_serialize_long_text_event() {
struct control_event event;
event.type = CONTROL_EVENT_TYPE_TEXT;
char text[TEXT_MAX_LENGTH + 1];
char text[TEXT_MAX_LENGTH];
memset(text, 'a', sizeof(text));
text[TEXT_MAX_LENGTH] = '\0';
event.text_event.text = text;
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
int size = control_event_serialize(&event, buf);
assert(size == 3 + TEXT_MAX_LENGTH);
assert(size == 3 + sizeof(text));
unsigned char expected[3 + TEXT_MAX_LENGTH];
expected[0] = 0x01; // CONTROL_EVENT_TYPE_KEYCODE
@@ -67,7 +66,7 @@ static void test_serialize_long_text_event(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_mouse_event(void) {
static void test_serialize_mouse_event() {
struct control_event event = {
.type = CONTROL_EVENT_TYPE_MOUSE,
.mouse_event = {
@@ -88,19 +87,19 @@ static void test_serialize_mouse_event(void) {
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
int size = control_event_serialize(&event, buf);
assert(size == 18);
assert(size == 14);
const unsigned char expected[] = {
0x02, // CONTROL_EVENT_TYPE_MOUSE
0x00, // AKEY_EVENT_ACTION_DOWN
0x00, 0x00, 0x00, 0x01, // AMOTION_EVENT_BUTTON_PRIMARY
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026
0x01, 0x04, 0x04, 0x02, // 260 1026
0x04, 0x38, 0x07, 0x80, // 1080 1920
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_scroll_event(void) {
static void test_serialize_scroll_event() {
struct control_event event = {
.type = CONTROL_EVENT_TYPE_SCROLL,
.scroll_event = {
@@ -121,11 +120,11 @@ static void test_serialize_scroll_event(void) {
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
int size = control_event_serialize(&event, buf);
assert(size == 21);
assert(size == 17);
const unsigned char expected[] = {
0x03, // CONTROL_EVENT_TYPE_SCROLL
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026
0x01, 0x04, 0x04, 0x02, // 260 1026
0x04, 0x38, 0x07, 0x80, // 1080 1920
0x00, 0x00, 0x00, 0x01, // 1
0xFF, 0xFF, 0xFF, 0xFF, // -1
@@ -133,10 +132,11 @@ static void test_serialize_scroll_event(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
int main(void) {
int main() {
test_serialize_keycode_event();
test_serialize_text_event();
test_serialize_long_text_event();
test_serialize_mouse_event();
test_serialize_scroll_event();
return 0;
}

View File

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

View File

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

View File

@@ -15,6 +15,6 @@ cpu = 'i686'
endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.1-win32-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.1-win32-dev'
prebuilt_sdl2 = 'SDL2-2.0.9/i686-w64-mingw32'
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

@@ -15,6 +15,6 @@ cpu = 'x86_64'
endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.1-win64-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.1-win64-dev'
prebuilt_sdl2 = 'SDL2-2.0.9/x86_64-w64-mingw32'
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

@@ -15,4 +15,3 @@ org.gradle.jvmargs=-Xmx1536m
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

View File

@@ -1,6 +1,6 @@
#Thu Apr 18 11:45:59 CEST 2019
#Mon Jun 04 11:48:32 CEST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

View File

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

View File

@@ -10,31 +10,31 @@ prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32
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.1-win32-shared.zip \
e692b18c01745d262c03294b382fd64df68fabe3c66aa4546a3ad3935175cde3 \
ffmpeg-4.1-win32-shared
@./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.1-win32-dev.zip \
34bc5e471fb9160609abd6bc271e361050f3ff7376b1b8a0873cca02b38277c8 \
ffmpeg-4.1-win32-dev
@./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.1-win64-shared.zip \
c4908c97436c946509dc365e421159274fa4b1e66dce6fb5b63d82a6294d5357 \
ffmpeg-4.1-win64-shared
@./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.1-win64-dev.zip \
761ec79aa3dae66698c9791a2f0bb9da8794246f8356cadc741ddc0eabab0471 \
ffmpeg-4.1-win64-dev
@./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.9-mingw.tar.gz \
0f9f00d0f2a9a95dfb5cce929718210c3f85432cc2e9d4abade4adcb7f6bb39d \
SDL2-2.0.9
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.8-mingw.tar.gz \
ffff7305d634aff5e1df5b7bb935435c3a02c8b03ad94a1a2be9169a558a7961 \
SDL2-2.0.8
prepare-adb:
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r28.0.1-windows.zip \
db78f726d5dc653706dcd15a462ab1b946c643f598df76906c4c1858411c54df \
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r28.0.0-windows.zip \
e2c1ec7c8e9b71cf1c8befd3bff91d06b26dd334c3f32b3817e9d46ba260b0e8 \
platform-tools

View File

@@ -55,4 +55,4 @@ get_dep() {
fi
}
get_dep "$url" "$sum" "$dir"
get_dep "$1" "$2" "$3"

View File

@@ -1,44 +0,0 @@
#!/bin/bash
set -e
# test locally
TESTDIR=build_test
rm -rf "$TESTDIR"
# run client tests with ASAN enabled
meson "$TESTDIR" -Db_sanitize=address
ninja -C"$TESTDIR" test
# test server
GRADLE=${GRADLE:-./gradlew}
$GRADLE -p server check
BUILDDIR=build_release
rm -rf "$BUILDDIR"
meson "$BUILDDIR" --buildtype release --strip -Db_lto=true
cd "$BUILDDIR"
ninja
cd -
# build Windows releases
make -f Makefile.CrossWindows
# the generated server must be the same everywhere
cmp "$BUILDDIR/server/scrcpy-server.jar" dist/scrcpy-win32/scrcpy-server.jar
cmp "$BUILDDIR/server/scrcpy-server.jar" dist/scrcpy-win64/scrcpy-server.jar
# get version name
TAG=$(git describe --tags --always)
# create release directory
mkdir -p "release-$TAG"
cp "$BUILDDIR/server/scrcpy-server.jar" "release-$TAG/scrcpy-server-$TAG.jar"
cp "dist/scrcpy-win32-$TAG.zip" "release-$TAG/"
cp "dist/scrcpy-win64-$TAG.zip" "release-$TAG/"
# generate checksums
cd "release-$TAG"
sha256sum "scrcpy-server-$TAG.jar" \
"scrcpy-win32-$TAG.zip" \
"scrcpy-win64-$TAG.zip" > SHA256SUMS.txt
echo "Release generated in release-$TAG/"

View File

@@ -6,8 +6,8 @@ android {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 27
versionCode 9
versionName "1.8"
versionCode 4
versionName "1.3"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@@ -10,10 +10,6 @@ if prebuilt_server == ''
install: true,
install_dir: 'share/scrcpy')
else
if not prebuilt_server.startswith('/')
# relative path needs some trick
prebuilt_server = meson.source_root() + '/' + prebuilt_server
endif
custom_target('scrcpy-server-prebuilt',
input: prebuilt_server,
output: 'scrcpy-server.jar',

View File

@@ -12,8 +12,6 @@ public final class ControlEvent {
public static final int TYPE_COMMAND = 4;
public static final int COMMAND_BACK_OR_SCREEN_ON = 0;
public static final int COMMAND_EXPAND_NOTIFICATION_PANEL = 1;
public static final int COMMAND_COLLAPSE_NOTIFICATION_PANEL = 2;
private int type;
private String text;

View File

@@ -9,8 +9,8 @@ import java.nio.charset.StandardCharsets;
public class ControlEventReader {
private static final int KEYCODE_PAYLOAD_LENGTH = 9;
private static final int MOUSE_PAYLOAD_LENGTH = 17;
private static final int SCROLL_PAYLOAD_LENGTH = 20;
private static final int MOUSE_PAYLOAD_LENGTH = 13;
private static final int SCROLL_PAYLOAD_LENGTH = 16;
private static final int COMMAND_PAYLOAD_LENGTH = 1;
public static final int TEXT_MAX_LENGTH = 300;
@@ -132,8 +132,8 @@ public class ControlEventReader {
}
private static Position readPosition(ByteBuffer buffer) {
int x = buffer.getInt();
int y = buffer.getInt();
int x = toUnsigned(buffer.getShort());
int y = toUnsigned(buffer.getShort());
int screenWidth = toUnsigned(buffer.getShort());
int screenHeight = toUnsigned(buffer.getShort());
return new Position(x, y, screenWidth, screenHeight);

View File

@@ -16,20 +16,16 @@ public final class DesktopConnection implements Closeable {
private static final String SOCKET_NAME = "scrcpy";
private final LocalSocket videoSocket;
private final FileDescriptor videoFd;
private final LocalSocket controlSocket;
private final InputStream controlInputStream;
private final LocalSocket socket;
private final InputStream inputStream;
private final FileDescriptor fd;
private final ControlEventReader reader = new ControlEventReader();
private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException {
this.videoSocket = videoSocket;
this.controlSocket = controlSocket;
controlInputStream = controlSocket.getInputStream();
videoFd = videoSocket.getFileDescriptor();
private DesktopConnection(LocalSocket socket) throws IOException {
this.socket = socket;
inputStream = socket.getInputStream();
fd = socket.getFileDescriptor();
}
private static LocalSocket connect(String abstractName) throws IOException {
@@ -48,46 +44,25 @@ public final class DesktopConnection implements Closeable {
}
public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException {
LocalSocket videoSocket;
LocalSocket controlSocket;
LocalSocket socket;
if (tunnelForward) {
LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME);
try {
videoSocket = localServerSocket.accept();
// send one byte so the client may read() to detect a connection error
videoSocket.getOutputStream().write(0);
try {
controlSocket = localServerSocket.accept();
} catch (IOException | RuntimeException e) {
videoSocket.close();
throw e;
}
} finally {
localServerSocket.close();
}
socket = listenAndAccept(SOCKET_NAME);
// send one byte so the client may read() to detect a connection error
socket.getOutputStream().write(0);
} else {
videoSocket = connect(SOCKET_NAME);
try {
controlSocket = connect(SOCKET_NAME);
} catch (IOException | RuntimeException e) {
videoSocket.close();
throw e;
}
socket = connect(SOCKET_NAME);
}
DesktopConnection connection = new DesktopConnection(videoSocket, controlSocket);
DesktopConnection connection = new DesktopConnection(socket);
Size videoSize = device.getScreenInfo().getVideoSize();
connection.send(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
return connection;
}
public void close() throws IOException {
videoSocket.shutdownInput();
videoSocket.shutdownOutput();
videoSocket.close();
controlSocket.shutdownInput();
controlSocket.shutdownOutput();
controlSocket.close();
socket.shutdownInput();
socket.shutdownOutput();
socket.close();
}
@SuppressWarnings("checkstyle:MagicNumber")
@@ -103,17 +78,17 @@ 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(videoFd, buffer, 0, buffer.length);
IO.writeFully(fd, buffer, 0, buffer.length);
}
public FileDescriptor getVideoFd() {
return videoFd;
public FileDescriptor getFd() {
return fd;
}
public ControlEvent receiveControlEvent() throws IOException {
ControlEvent event = reader.next();
while (event == null) {
reader.readFrom(controlInputStream);
reader.readFrom(inputStream);
event = reader.next();
}
return event;

View File

@@ -132,14 +132,6 @@ public final class Device {
this.rotationListener = rotationListener;
}
public void expandNotificationPanel() {
serviceManager.getStatusBarManager().expandNotificationsPanel();
}
public void collapsePanels() {
serviceManager.getStatusBarManager().collapsePanels();
}
static Rect flipRect(Rect crop) {
return new Rect(crop.top, crop.left, crop.bottom, crop.right);
}

View File

@@ -104,16 +104,13 @@ public class EventController {
return true;
}
private int injectText(String text) {
int successCount = 0;
private boolean injectText(String text) {
for (char c : text.toCharArray()) {
if (!injectChar(c)) {
Ln.w("Could not inject char u+" + String.format("%04x", (int) c));
continue;
return false;
}
successCount++;
}
return successCount;
return true;
}
private boolean injectMouse(int action, int buttons, Position position) {
@@ -175,12 +172,6 @@ public class EventController {
switch (action) {
case ControlEvent.COMMAND_BACK_OR_SCREEN_ON:
return pressBackOrTurnScreenOn();
case ControlEvent.COMMAND_EXPAND_NOTIFICATION_PANEL:
device.expandNotificationPanel();
return true;
case ControlEvent.COMMAND_COLLAPSE_NOTIFICATION_PANEL:
device.collapsePanels();
return true;
default:
Ln.w("Unsupported command: " + action);
}

View File

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

View File

@@ -52,13 +52,7 @@ public final class Ln {
if (isEnabled(Level.ERROR)) {
Log.e(TAG, message, throwable);
System.out.println("ERROR: " + message);
if (throwable != null) {
throwable.printStackTrace();
}
throwable.printStackTrace();
}
}
public static void e(String message) {
e(message, null);
}
}

View File

@@ -7,7 +7,6 @@ public class Options {
private int bitRate;
private boolean tunnelForward;
private Rect crop;
private boolean sendFrameMeta; // send PTS so that the client may record properly
public int getMaxSize() {
return maxSize;
@@ -40,12 +39,4 @@ public class Options {
public void setCrop(Rect crop) {
this.crop = crop;
}
public boolean getSendFrameMeta() {
return sendFrameMeta;
}
public void setSendFrameMeta(boolean sendFrameMeta) {
this.sendFrameMeta = sendFrameMeta;
}
}

View File

@@ -22,26 +22,21 @@ public class ScreenEncoder implements Device.RotationListener {
private static final int REPEAT_FRAME_DELAY = 6; // repeat after 6 frames
private static final int MICROSECONDS_IN_ONE_SECOND = 1_000_000;
private static final int NO_PTS = -1;
private final AtomicBoolean rotationChanged = new AtomicBoolean();
private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
private int bitRate;
private int frameRate;
private int iFrameInterval;
private boolean sendFrameMeta;
private long ptsOrigin;
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int frameRate, int iFrameInterval) {
this.sendFrameMeta = sendFrameMeta;
public ScreenEncoder(int bitRate, int frameRate, int iFrameInterval) {
this.bitRate = bitRate;
this.frameRate = frameRate;
this.iFrameInterval = iFrameInterval;
}
public ScreenEncoder(boolean sendFrameMeta, int bitRate) {
this(sendFrameMeta, bitRate, DEFAULT_FRAME_RATE, DEFAULT_I_FRAME_INTERVAL);
public ScreenEncoder(int bitRate) {
this(bitRate, DEFAULT_FRAME_RATE, DEFAULT_I_FRAME_INTERVAL);
}
@Override
@@ -70,9 +65,8 @@ public class ScreenEncoder implements Device.RotationListener {
codec.start();
try {
alive = encode(codec, fd);
// do not call stop() on exception, it would trigger an IllegalStateException
codec.stop();
} finally {
codec.stop();
destroyDisplay(display);
codec.release();
surface.release();
@@ -86,7 +80,6 @@ public class ScreenEncoder implements Device.RotationListener {
private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException {
boolean eof = false;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while (!consumeRotationChange() && !eof) {
int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
eof = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
@@ -97,11 +90,6 @@ public class ScreenEncoder implements Device.RotationListener {
}
if (outputBufferId >= 0) {
ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId);
if (sendFrameMeta) {
writeFrameMeta(fd, bufferInfo, codecBuffer.remaining());
}
IO.writeFully(fd, codecBuffer);
}
} finally {
@@ -114,25 +102,6 @@ public class ScreenEncoder implements Device.RotationListener {
return !eof;
}
private void writeFrameMeta(FileDescriptor fd, MediaCodec.BufferInfo bufferInfo, int packetSize) throws IOException {
headerBuffer.clear();
long pts;
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
pts = NO_PTS; // non-media data packet
} else {
if (ptsOrigin == 0) {
ptsOrigin = bufferInfo.presentationTimeUs;
}
pts = bufferInfo.presentationTimeUs - ptsOrigin;
}
headerBuffer.putLong(pts);
headerBuffer.putInt(packetSize);
headerBuffer.flip();
IO.writeFully(fd, headerBuffer);
}
private static MediaCodec createCodec() throws IOException {
return MediaCodec.createEncoderByType("video/avc");
}
@@ -150,7 +119,7 @@ public class ScreenEncoder implements Device.RotationListener {
}
private static IBinder createDisplay() {
return SurfaceControl.createDisplay("scrcpy", true);
return SurfaceControl.createDisplay("scrcpy", false);
}
private static void configure(MediaCodec codec, MediaFormat format) {

View File

@@ -2,13 +2,10 @@ package com.genymobile.scrcpy;
import android.graphics.Rect;
import java.io.File;
import java.io.IOException;
public final class Server {
private static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar";
private Server() {
// not instantiable
}
@@ -17,14 +14,14 @@ public final class Server {
final Device device = new Device(options);
boolean tunnelForward = options.isTunnelForward();
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate());
ScreenEncoder screenEncoder = new ScreenEncoder(options.getBitRate());
// asynchronous
startEventController(device, connection);
try {
// synchronous
screenEncoder.streamScreen(device, connection.getVideoFd());
screenEncoder.streamScreen(device, connection.getFd());
} catch (IOException e) {
// this is expected on close
Ln.d("Screen streaming stopped");
@@ -48,34 +45,37 @@ public final class Server {
@SuppressWarnings("checkstyle:MagicNumber")
private static Options createOptions(String... args) {
if (args.length != 5) {
throw new IllegalArgumentException("Expecting 5 parameters");
}
Options options = new Options();
if (args.length < 1) {
return options;
}
int maxSize = Integer.parseInt(args[0]) & ~7; // multiple of 8
options.setMaxSize(maxSize);
if (args.length < 2) {
return options;
}
int bitRate = Integer.parseInt(args[1]);
options.setBitRate(bitRate);
if (args.length < 3) {
return options;
}
// use "adb forward" instead of "adb tunnel"? (so the server must listen)
boolean tunnelForward = Boolean.parseBoolean(args[2]);
options.setTunnelForward(tunnelForward);
if (args.length < 4) {
return options;
}
Rect crop = parseCrop(args[3]);
options.setCrop(crop);
boolean sendFrameMeta = Boolean.parseBoolean(args[4]);
options.setSendFrameMeta(sendFrameMeta);
return options;
}
@SuppressWarnings("checkstyle:MagicNumber")
private static Rect parseCrop(String crop) {
if ("-".equals(crop)) {
if (crop.isEmpty()) {
return null;
}
// input format: "width:height:x:y"
@@ -90,14 +90,6 @@ public final class Server {
return new Rect(x, y, x + width, y + height);
}
private static void unlinkSelf() {
try {
new File(SERVER_PATH).delete();
} catch (Exception e) {
Ln.e("Cannot unlink server", e);
}
}
public static void main(String... args) throws Exception {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
@@ -106,7 +98,6 @@ public final class Server {
}
});
unlinkSelf();
Options options = createOptions(args);
scrcpy(options);
}

View File

@@ -14,7 +14,6 @@ public final class ServiceManager {
private DisplayManager displayManager;
private InputManager inputManager;
private PowerManager powerManager;
private StatusBarManager statusBarManager;
public ServiceManager() {
try {
@@ -61,11 +60,4 @@ public final class ServiceManager {
}
return powerManager;
}
public StatusBarManager getStatusBarManager() {
if (statusBarManager == null) {
statusBarManager = new StatusBarManager(getService("statusbar", "com.android.internal.statusbar.IStatusBarService"));
}
return statusBarManager;
}
}

View File

@@ -1,51 +0,0 @@
package com.genymobile.scrcpy.wrappers;
import android.os.IInterface;
import com.genymobile.scrcpy.Ln;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class StatusBarManager {
private final IInterface manager;
private Method expandNotificationsPanelMethod;
private Method collapsePanelsMethod;
public StatusBarManager(IInterface manager) {
this.manager = manager;
}
public void expandNotificationsPanel() {
if (expandNotificationsPanelMethod == null) {
try {
expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
} catch (NoSuchMethodException e) {
Ln.e("ServiceBarManager.expandNotificationsPanel() is not available on this device");
return;
}
}
try {
expandNotificationsPanelMethod.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Cannot invoke ServiceBarManager.expandNotificationsPanel()", e);
}
}
public void collapsePanels() {
if (collapsePanelsMethod == null) {
try {
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
} catch (NoSuchMethodException e) {
Ln.e("ServiceBarManager.collapsePanels() is not available on this device");
return;
}
}
try {
collapsePanelsMethod.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Cannot invoke ServiceBarManager.collapsePanels()", e);
}
}
}