Compare commits
10 Commits
api19
...
hidpiscale
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae87885ffa | ||
|
|
65021416b3 | ||
|
|
305e76ee6e | ||
|
|
ef0029d10e | ||
|
|
36c68c4802 | ||
|
|
a82f665114 | ||
|
|
f88cc91a51 | ||
|
|
38e4b3e39a | ||
|
|
64efe2c07d | ||
|
|
93e9fb496c |
38
DEVELOP.md
38
DEVELOP.md
@@ -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
|
args)`][main] method), compiled against the Android framework, and executed as
|
||||||
`shell` on the Android device.
|
`shell` on the Android device.
|
||||||
|
|
||||||
[main]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/Server.java#L61
|
[main]: server/src/main/java/com/genymobile/scrcpy/Server.java#L61
|
||||||
|
|
||||||
To run such a Java application, the classes must be [_dexed_][dex] (typically,
|
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
|
to `classes.dex`). If `my.package.MainClass` is the main class, compiled to
|
||||||
@@ -57,7 +57,7 @@ build system, the server is built to an (unsigned) APK (renamed to
|
|||||||
|
|
||||||
### Hidden methods
|
### Hidden methods
|
||||||
|
|
||||||
Although compiled against the Android framework, [hidden] methods and classes are
|
Albeit compiled against the Android framework, [hidden] methods and classes are
|
||||||
not directly accessible (and they may differ from one Android version to
|
not directly accessible (and they may differ from one Android version to
|
||||||
another).
|
another).
|
||||||
|
|
||||||
@@ -65,8 +65,8 @@ They can be called using reflection though. The communication with hidden
|
|||||||
components is provided by [_wrappers_ classes][wrappers] and [aidl].
|
components is provided by [_wrappers_ classes][wrappers] and [aidl].
|
||||||
|
|
||||||
[hidden]: https://stackoverflow.com/a/31908373/1987178
|
[hidden]: https://stackoverflow.com/a/31908373/1987178
|
||||||
[wrappers]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/wrappers
|
[wrappers]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/server/src/main/java/com/genymobile/scrcpy/wrappers
|
||||||
[aidl]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/aidl/android/view
|
[aidl]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/server/src/main/aidl/android/view
|
||||||
|
|
||||||
|
|
||||||
### Threading
|
### 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
|
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).
|
stream to the provided output stream (the socket connected to the client).
|
||||||
|
|
||||||
[`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
|
[`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
|
||||||
[`MediaCodec`]: https://developer.android.com/reference/android/media/MediaCodec.html
|
[`MediaCodec`]: https://developer.android.com/reference/android/media/MediaCodec.html
|
||||||
[surface]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L63-L64
|
[surface]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L63-L64
|
||||||
|
|
||||||
On device [rotation], the codec, surface and display are reinitialized, and a
|
On device [rotation], the codec, surface and display are reinitialized, and a
|
||||||
new video stream is produced.
|
new video stream is produced.
|
||||||
@@ -105,8 +105,8 @@ because it avoids to send unnecessary frames, but there are drawbacks:
|
|||||||
Both problems are [solved][repeat] by the flag
|
Both problems are [solved][repeat] by the flag
|
||||||
[`KEY_REPEAT_PREVIOUS_FRAME_AFTER`][repeat-flag].
|
[`KEY_REPEAT_PREVIOUS_FRAME_AFTER`][repeat-flag].
|
||||||
|
|
||||||
[rotation]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L89-L92
|
[rotation]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/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]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/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
|
[repeat-flag]: https://developer.android.com/reference/android/media/MediaFormat.html#KEY_REPEAT_PREVIOUS_FRAME_AFTER
|
||||||
|
|
||||||
|
|
||||||
@@ -124,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
|
the _hidden_ method [`InputManager.injectInputEvent`] (exposed by our
|
||||||
[`InputManager` wrapper][inject-wrapper]).
|
[`InputManager` wrapper][inject-wrapper]).
|
||||||
|
|
||||||
[`EventController`]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/EventController.java#L70
|
[`EventController`]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/server/src/main/java/com/genymobile/scrcpy/EventController.java#L70
|
||||||
[`KeyEvent`]: https://developer.android.com/reference/android/view/KeyEvent.html
|
[`KeyEvent`]: https://developer.android.com/reference/android/view/KeyEvent.html
|
||||||
[`MotionEvent`]: https://developer.android.com/reference/android/view/MotionEvent.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
|
[`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.0/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27
|
[inject-wrapper]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -195,8 +195,8 @@ When a new decoded frame is available, the decoder _swaps_ the decoding and
|
|||||||
rendering frame (with proper synchronization). Thus, it immediatly starts
|
rendering frame (with proper synchronization). Thus, it immediatly starts
|
||||||
to decode a new frame while the main thread renders the last one.
|
to decode a new frame while the main thread renders the last one.
|
||||||
|
|
||||||
[decoder]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/decoder.c
|
[decoder]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/app/src/decoder.c
|
||||||
[frames]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/frames.h
|
[frames]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/app/src/frames.h
|
||||||
|
|
||||||
|
|
||||||
### Controller
|
### Controller
|
||||||
@@ -211,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
|
controller takes events from the queue, that it serializes and sends to the
|
||||||
client.
|
client.
|
||||||
|
|
||||||
[controller]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/controller.h
|
[controller]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/app/src/controller.h
|
||||||
[controlevent]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/controlevent.h
|
[controlevent]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/app/src/controlevent.h
|
||||||
[inputmanager]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/inputmanager.h
|
[inputmanager]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/app/src/inputmanager.h
|
||||||
[convert]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/convert.h
|
[convert]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/app/src/convert.h
|
||||||
|
|
||||||
|
|
||||||
### UI and event loop
|
### UI and event loop
|
||||||
@@ -225,9 +225,9 @@ thread.
|
|||||||
Events are handled in the [event loop], which either updates the [screen] or
|
Events are handled in the [event loop], which either updates the [screen] or
|
||||||
delegates to the [input manager][inputmanager].
|
delegates to the [input manager][inputmanager].
|
||||||
|
|
||||||
[scrcpy]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/scrcpy.c
|
[scrcpy]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/app/src/scrcpy.c
|
||||||
[event loop]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/scrcpy.c#L38
|
[event loop]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/app/src/scrcpy.c#L38
|
||||||
[screen]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/screen.h
|
[screen]: https://github.com/Genymobile/scrcpy/blob/079d750d41b7849eb1b9caaa6151ef2429581584/app/src/screen.h
|
||||||
|
|
||||||
|
|
||||||
## Hack
|
## Hack
|
||||||
|
|||||||
81
FAQ.md
81
FAQ.md
@@ -1,81 +0,0 @@
|
|||||||
# Frequently Asked Questions
|
|
||||||
|
|
||||||
## Common issues
|
|
||||||
|
|
||||||
The application is very young, it is not unlikely that you encounter problems
|
|
||||||
with it.
|
|
||||||
|
|
||||||
Here are the common reported problems and their status.
|
|
||||||
|
|
||||||
### On Windows, I have no output in the console
|
|
||||||
|
|
||||||
When run in `cmd.exe`, the application does not print anything. Even `scrcpy
|
|
||||||
--help` have no output. We don't know why yet.
|
|
||||||
|
|
||||||
However, if you run the very same `scrcpy.exe` from
|
|
||||||
[MSYS2](https://www.msys2.org/) (`mingw64`), then it correctly prints output.
|
|
||||||
|
|
||||||
As a workaround, redirect outputs to files, so that you can read the files
|
|
||||||
afterwards:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy >stdout 2>stderr
|
|
||||||
type stdout
|
|
||||||
type stderr
|
|
||||||
```
|
|
||||||
|
|
||||||
_Note that all SDL logs are printed to stderr._
|
|
||||||
|
|
||||||
|
|
||||||
### On Windows, when I start the application, nothing happens
|
|
||||||
|
|
||||||
The previous problem does not help to get a clue about the cause.
|
|
||||||
|
|
||||||
The most common is your device not being detected by `adb`, or is unauthorized.
|
|
||||||
Check everything is ok by calling:
|
|
||||||
|
|
||||||
adb devices
|
|
||||||
|
|
||||||
Windows may need some [drivers] to detect your device.
|
|
||||||
|
|
||||||
[drivers]: https://developer.android.com/studio/run/oem-usb.html
|
|
||||||
|
|
||||||
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].
|
|
||||||
|
|
||||||
[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
|
|
||||||
|
|
||||||
|
|
||||||
### Mouse clicks at wrong location
|
|
||||||
|
|
||||||
On MacOS, with HiDPI support and multiple screens, input location are wrongly
|
|
||||||
scaled. See [issue 15].
|
|
||||||
|
|
||||||
[issue 15]: https://github.com/Genymobile/scrcpy/issues/15
|
|
||||||
|
|
||||||
A workaround is to build with HiDPI support disabled:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
meson x --buildtype release -Dhidpi_support=false
|
|
||||||
```
|
|
||||||
|
|
||||||
However, the video will be displayed at lower resolution.
|
|
||||||
247
README.md
247
README.md
@@ -2,35 +2,34 @@
|
|||||||
|
|
||||||
This application provides display and control of Android devices connected on
|
This application provides display and control of Android devices connected on
|
||||||
USB. It does not require any _root_ access. It works on _GNU/Linux_, _Windows_
|
USB. It does not require any _root_ access. It works on _GNU/Linux_, _Windows_
|
||||||
and _MacOS_.
|
and _Mac OS_.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
The Android part requires at least API 19 (Android 4.4).
|
The Android part requires at least API 21 (Android 5.0).
|
||||||
|
|
||||||
You need [adb]. It is available in the [Android SDK platform
|
You need [adb] (recent enough so that `adb reverse` is implemented, it works
|
||||||
tools][platform-tools], or packaged in your distribution (`android-adb-tools`).
|
with 1.0.36). It is available in the [Android SDK platform
|
||||||
|
tools][platform-tools], on packaged in your distribution (`android-adb-tools`).
|
||||||
|
|
||||||
On Windows, if you use the [prebuilt application](#windows), it is already
|
On Windows, just download the [platform-tools][platform-tools-windows] and
|
||||||
included. Otherwise, just download the [platform-tools][platform-tools-windows]
|
extract the following files to a directory accessible from your `PATH`:
|
||||||
and extract the following files to a directory accessible from your `PATH`:
|
|
||||||
- `adb.exe`
|
- `adb.exe`
|
||||||
- `AdbWinApi.dll`
|
- `AdbWinApi.dll`
|
||||||
- `AdbWinUsbApi.dll`
|
- `AdbWinUsbApi.dll`
|
||||||
|
|
||||||
Make sure you [enabled adb debugging][enable-adb] on your device(s).
|
Make sure you [enabled adb debugging][enable-adb] on your device(s).
|
||||||
|
|
||||||
The client requires [FFmpeg] and [LibSDL2].
|
|
||||||
|
|
||||||
[adb]: https://developer.android.com/studio/command-line/adb.html
|
[adb]: https://developer.android.com/studio/command-line/adb.html
|
||||||
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
|
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
|
||||||
[platform-tools]: https://developer.android.com/studio/releases/platform-tools.html
|
[platform-tools]: https://developer.android.com/studio/releases/platform-tools.html
|
||||||
[platform-tools-windows]: https://dl.google.com/android/repository/platform-tools-latest-windows.zip
|
[platform-tools-windows]: https://dl.google.com/android/repository/platform-tools-latest-windows.zip
|
||||||
[ffmpeg]: https://en.wikipedia.org/wiki/FFmpeg
|
|
||||||
[LibSDL2]: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
|
The client requires _FFmpeg_ and _LibSDL2_.
|
||||||
|
|
||||||
|
|
||||||
## Build and install
|
## Build and install
|
||||||
|
|
||||||
@@ -38,81 +37,43 @@ The client requires [FFmpeg] and [LibSDL2].
|
|||||||
|
|
||||||
#### Linux
|
#### Linux
|
||||||
|
|
||||||
Install the required packages from your package manager.
|
Install the required packages from your package manager (here, for Debian):
|
||||||
|
|
||||||
##### Debian/Ubuntu
|
# runtime dependencies
|
||||||
|
sudo apt install ffmpeg libsdl2-2.0.0
|
||||||
|
|
||||||
```bash
|
# build dependencies
|
||||||
# runtime dependencies
|
sudo apt install make gcc openjdk-8-jdk pkg-config meson zip \
|
||||||
sudo apt install ffmpeg libsdl2-2.0.0
|
libavcodec-dev libavformat-dev libavutil-dev \
|
||||||
|
libsdl2-dev
|
||||||
# client build dependencies
|
|
||||||
sudo apt install make gcc pkg-config meson \
|
|
||||||
libavcodec-dev libavformat-dev libavutil-dev \
|
|
||||||
libsdl2-dev
|
|
||||||
|
|
||||||
# server build dependencies
|
|
||||||
sudo apt install openjdk-8-jdk
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Fedora
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# enable RPM fusion free
|
|
||||||
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
|
|
||||||
|
|
||||||
# client build dependencies
|
|
||||||
sudo dnf install SDL2-devel ffms2-devel meson gcc make
|
|
||||||
|
|
||||||
# server build dependencies
|
|
||||||
sudo dnf install java
|
|
||||||
```
|
|
||||||
|
|
||||||
##### 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
|
|
||||||
|
|
||||||
|
|
||||||
#### Windows
|
#### Windows
|
||||||
|
|
||||||
For Windows, for simplicity, a prebuilt archive with all the dependencies
|
For Windows, for simplicity, a prebuilt package with all the dependencies
|
||||||
(including `adb`) is available:
|
(including `adb`) is available: TODO.
|
||||||
|
|
||||||
- [`scrcpy-windows-with-deps-v1.1.zip`][direct-windows-with-deps].
|
|
||||||
_(SHA-256: 27eb36c15937601d1062c1dc0b45faae0e06fefea2019aadeb4fa7f76a07bb4c)_
|
|
||||||
|
|
||||||
[direct-windows-with-deps]: https://github.com/Genymobile/scrcpy/releases/download/v1.1/scrcpy-windows-with-deps-v1.1.zip
|
|
||||||
|
|
||||||
_(It's just a portable version including _dll_ copied from MSYS2.)_
|
|
||||||
|
|
||||||
Instead, you may want to build it manually. You need [MSYS2] to build the
|
Instead, you may want to build it manually. You need [MSYS2] to build the
|
||||||
project. From an MSYS2 terminal, install the required packages:
|
project. From an MSYS2 terminal, install the required packages:
|
||||||
|
|
||||||
[MSYS2]: http://www.msys2.org/
|
[MSYS2]: http://www.msys2.org/
|
||||||
|
|
||||||
```bash
|
# runtime dependencies
|
||||||
# runtime dependencies
|
pacman -S mingw-w64-x86_64-SDL2 \
|
||||||
pacman -S mingw-w64-x86_64-SDL2 \
|
mingw-w64-x86_64-ffmpeg
|
||||||
mingw-w64-x86_64-ffmpeg
|
|
||||||
|
|
||||||
# client build dependencies
|
# build dependencies
|
||||||
pacman -S mingw-w64-x86_64-make \
|
pacman -S mingw-w64-x86_64-make \
|
||||||
mingw-w64-x86_64-gcc \
|
mingw-w64-x86_64-gcc \
|
||||||
mingw-w64-x86_64-pkg-config \
|
mingw-w64-x86_64-pkg-config \
|
||||||
mingw-w64-x86_64-meson
|
mingw-w64-x86_64-meson \
|
||||||
```
|
zip
|
||||||
|
|
||||||
Java (>= 7) is not available in MSYS2, so if you plan to build the server,
|
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`:
|
install it manually and make it available from the `PATH`:
|
||||||
|
|
||||||
```bash
|
export PATH="$JAVA_HOME/bin:$PATH"
|
||||||
export PATH="$JAVA_HOME/bin:$PATH"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Mac OS
|
#### Mac OS
|
||||||
|
|
||||||
@@ -120,27 +81,17 @@ Use [Homebrew] to install the packages:
|
|||||||
|
|
||||||
[Homebrew]: https://brew.sh/
|
[Homebrew]: https://brew.sh/
|
||||||
|
|
||||||
```bash
|
# runtime dependencies
|
||||||
# runtime dependencies
|
brew install sdl2 ffmpeg
|
||||||
brew install sdl2 ffmpeg
|
|
||||||
|
|
||||||
# client build dependencies
|
# build dependencies
|
||||||
brew install pkg-config meson
|
brew install gcc pkg-config meson zip
|
||||||
```
|
|
||||||
|
|
||||||
Additionally, if you want to build the server, install Java 8 from Caskroom, and
|
Java (>= 7) is not available in Homebrew, so if you plan to build the server,
|
||||||
make it avaliable from the `PATH`:
|
install it manually and make it available from the `PATH`:
|
||||||
|
|
||||||
```bash
|
export PATH="$JAVA_HOME/bin:$PATH"
|
||||||
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
|
### Common steps
|
||||||
|
|
||||||
@@ -149,36 +100,21 @@ its directory. For example:
|
|||||||
|
|
||||||
[Android SDK]: https://developer.android.com/studio/index.html
|
[Android SDK]: https://developer.android.com/studio/index.html
|
||||||
|
|
||||||
```bash
|
export ANDROID_HOME=~/android/sdk
|
||||||
export ANDROID_HOME=~/android/sdk
|
|
||||||
```
|
|
||||||
|
|
||||||
Clone the project:
|
Then, build `scrcpy`:
|
||||||
|
|
||||||
```bash
|
meson x --buildtype release --strip -Db_lto=true
|
||||||
git clone https://github.com/Genymobile/scrcpy
|
cd x
|
||||||
cd scrcpy
|
ninja
|
||||||
```
|
|
||||||
|
|
||||||
Then, build:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
meson x --buildtype release --strip -Db_lto=true
|
|
||||||
cd x
|
|
||||||
ninja
|
|
||||||
```
|
|
||||||
|
|
||||||
You can test it from here:
|
You can test it from here:
|
||||||
|
|
||||||
```bash
|
ninja run
|
||||||
ninja run
|
|
||||||
```
|
|
||||||
|
|
||||||
Or you can install it on the system:
|
Or you can install it on the system:
|
||||||
|
|
||||||
```bash
|
sudo ninja install # without sudo on Windows
|
||||||
sudo ninja install # without sudo on Windows
|
|
||||||
```
|
|
||||||
|
|
||||||
This installs two files:
|
This installs two files:
|
||||||
|
|
||||||
@@ -192,25 +128,19 @@ Just remove them to "uninstall" the application.
|
|||||||
|
|
||||||
Since the server binary, that will be pushed to the Android device, does not
|
Since the server binary, that will be pushed to the Android device, does not
|
||||||
depend on your system and architecture, you may want to use the prebuilt binary
|
depend on your system and architecture, you may want to use the prebuilt binary
|
||||||
instead:
|
instead: [`scrcpy-server.jar`](TODO).
|
||||||
|
|
||||||
- [`scrcpy-server-v1.1.jar`][direct-scrcpy-server].
|
|
||||||
_(SHA-256: 14826512bf38447ec94adf3b531676ce038d19e7e06757fb4e537882b17e77b3)_
|
|
||||||
|
|
||||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.1/scrcpy-server-v1.1.jar
|
|
||||||
|
|
||||||
In that case, the build does not require Java or the Android SDK.
|
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
|
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||||
configuration:
|
configuration:
|
||||||
|
|
||||||
```bash
|
meson x --buildtype release --strip -Db_lto=true \
|
||||||
meson x --buildtype release --strip -Db_lto=true \
|
-Dprebuilt_server=/path/to/scrcpy-server.jar
|
||||||
-Dprebuilt_server=/path/to/scrcpy-server.jar
|
cd x
|
||||||
cd x
|
ninja
|
||||||
ninja
|
sudo ninja install
|
||||||
sudo ninja install
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
@@ -218,70 +148,41 @@ _At runtime, `adb` must be accessible from your `PATH`._
|
|||||||
|
|
||||||
If everything is ok, just plug an Android device, and execute:
|
If everything is ok, just plug an Android device, and execute:
|
||||||
|
|
||||||
```bash
|
scrcpy
|
||||||
scrcpy
|
|
||||||
```
|
|
||||||
|
|
||||||
It accepts command-line arguments, listed by:
|
It accepts command-line arguments, listed by:
|
||||||
|
|
||||||
```bash
|
scrcpy --help
|
||||||
scrcpy --help
|
|
||||||
```
|
|
||||||
|
|
||||||
For example, to decrease video bitrate to 2Mbps (default is 8Mbps):
|
For example, to decrease video bitrate to 2Mbps (default is 8Mbps):
|
||||||
|
|
||||||
```bash
|
scrcpy -b 2M
|
||||||
scrcpy -b 2M
|
|
||||||
```
|
|
||||||
|
|
||||||
To limit the video dimensions (e.g. if the device is 2540×1440, but the host
|
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):
|
screen is smaller, or cannot decode such a high definition):
|
||||||
|
|
||||||
```bash
|
scrcpy -m 1024
|
||||||
scrcpy -m 1024
|
|
||||||
```
|
|
||||||
|
|
||||||
If several devices are listed in `adb devices`, you must specify the _serial_:
|
If several devices are listed in `adb devices`, you must specify the _serial_:
|
||||||
|
|
||||||
```bash
|
scrcpy -s 0123456789abcdef
|
||||||
scrcpy -s 0123456789abcdef
|
|
||||||
```
|
|
||||||
|
|
||||||
To show physical touches while scrcpy is running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy -t
|
|
||||||
```
|
|
||||||
|
|
||||||
To run without installing:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./run x [options]
|
|
||||||
```
|
|
||||||
|
|
||||||
(where `x` is your build directory).
|
|
||||||
|
|
||||||
|
|
||||||
## Shortcuts
|
## Shortcuts
|
||||||
|
|
||||||
| Action | Shortcut |
|
| Action | Shortcut |
|
||||||
| -------------------------------------- |:---------------------------- |
|
| ------------------------------------- | -------------:|
|
||||||
| switch fullscreen mode | `Ctrl`+`f` |
|
| switch fullscreen mode | `Ctrl`+`f` |
|
||||||
| resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` |
|
| resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` |
|
||||||
| resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ |
|
| resize window to remove black borders | `Ctrl`+`x` |
|
||||||
| click on `HOME` | `Ctrl`+`h` \| _Middle-click_ |
|
| click on `HOME` | `Ctrl`+`h` |
|
||||||
| click on `BACK` | `Ctrl`+`b` \| _Right-click²_ |
|
| click on `BACK` | `Ctrl`+`b` |
|
||||||
| click on `APP_SWITCH` | `Ctrl`+`s` |
|
| click on `APP_SWITCH` | `Ctrl`+`m` |
|
||||||
| click on `MENU` | `Ctrl`+`m` |
|
| click on `VOLUME_UP` | `Ctrl`+`+` |
|
||||||
| click on `VOLUME_UP` | `Ctrl`+`+` |
|
| click on `VOLUME_DOWN` | `Ctrl`+`-` |
|
||||||
| click on `VOLUME_DOWN` | `Ctrl`+`-` |
|
| click on `POWER` | `Ctrl`+`p` |
|
||||||
| click on `POWER` | `Ctrl`+`p` |
|
| turn screen on | _Right-click_ |
|
||||||
| turn screen on | _Right-click²_ |
|
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
|
||||||
| paste computer clipboard to device | `Ctrl`+`v` |
|
|
||||||
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
|
|
||||||
|
|
||||||
_¹Double-click on black borders to remove them._
|
|
||||||
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
|
||||||
|
|
||||||
|
|
||||||
## Why _scrcpy_?
|
## Why _scrcpy_?
|
||||||
@@ -294,11 +195,6 @@ A colleague challenged me to find a name as unpronounceable as [gnirehtet].
|
|||||||
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
|
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
|
||||||
|
|
||||||
|
|
||||||
## Common issues
|
|
||||||
|
|
||||||
See the [FAQ](FAQ.md).
|
|
||||||
|
|
||||||
|
|
||||||
## Developers
|
## Developers
|
||||||
|
|
||||||
Read the [developers page].
|
Read the [developers page].
|
||||||
@@ -321,8 +217,3 @@ Read the [developers page].
|
|||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
## Articles
|
|
||||||
|
|
||||||
- [Introducing scrcpy](https://blog.rom1v.com/2018/03/introducing-scrcpy/)
|
|
||||||
- [Scrcpy now works wirelessly](https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/)
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ src = [
|
|||||||
'src/device.c',
|
'src/device.c',
|
||||||
'src/fpscounter.c',
|
'src/fpscounter.c',
|
||||||
'src/frames.c',
|
'src/frames.c',
|
||||||
|
'src/hidpi.c',
|
||||||
'src/inputmanager.c',
|
'src/inputmanager.c',
|
||||||
'src/lockutil.c',
|
'src/lockutil.c',
|
||||||
'src/net.c',
|
'src/net.c',
|
||||||
@@ -42,7 +43,7 @@ conf = configuration_data()
|
|||||||
conf.set('BUILD_DEBUG', get_option('buildtype') == 'debug')
|
conf.set('BUILD_DEBUG', get_option('buildtype') == 'debug')
|
||||||
|
|
||||||
# the version, updated on release
|
# the version, updated on release
|
||||||
conf.set_quoted('SCRCPY_VERSION', '1.1')
|
conf.set_quoted('SCRCPY_VERSION', '0.1')
|
||||||
|
|
||||||
# the prefix used during configuration (meson --prefix=PREFIX)
|
# the prefix used during configuration (meson --prefix=PREFIX)
|
||||||
conf.set_quoted('PREFIX', get_option('prefix'))
|
conf.set_quoted('PREFIX', get_option('prefix'))
|
||||||
@@ -80,13 +81,9 @@ conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps
|
|||||||
# SKIP_FRAMES improves latency at the cost of framerate
|
# SKIP_FRAMES improves latency at the cost of framerate
|
||||||
conf.set('SKIP_FRAMES', get_option('skip_frames'))
|
conf.set('SKIP_FRAMES', get_option('skip_frames'))
|
||||||
|
|
||||||
# enable High DPI support
|
|
||||||
conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
|
|
||||||
|
|
||||||
configure_file(configuration: conf, output: 'config.h')
|
configure_file(configuration: conf, output: 'config.h')
|
||||||
|
|
||||||
src_dir = include_directories('src')
|
executable('scrcpy', src, dependencies: dependencies, install: true)
|
||||||
executable('scrcpy', src, dependencies: dependencies, include_directories: src_dir, install: true)
|
|
||||||
|
|
||||||
|
|
||||||
### TESTS
|
### TESTS
|
||||||
@@ -97,6 +94,8 @@ tests = [
|
|||||||
['test_strutil', ['tests/test_strutil.c', 'src/strutil.c']],
|
['test_strutil', ['tests/test_strutil.c', 'src/strutil.c']],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
src_dir = include_directories('src')
|
||||||
|
|
||||||
foreach t : tests
|
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)
|
test(t[0], exe)
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
|
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
|
||||||
|
|
||||||
static const char *adb_command;
|
static const char *adb_command;
|
||||||
|
|
||||||
static inline const char *get_adb_command() {
|
static inline const char *get_adb_command() {
|
||||||
@@ -44,13 +45,6 @@ process_t adb_forward(const char *serial, uint16_t local_port, const char *devic
|
|||||||
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
|
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
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 local[4 + 5 + 1]; // tcp:PORT
|
||||||
char remote[108 + 14 + 1]; // localabstract:NAME
|
char remote[108 + 14 + 1]; // localabstract:NAME
|
||||||
|
|||||||
@@ -7,11 +7,12 @@
|
|||||||
|
|
||||||
// <https://stackoverflow.com/a/44383330/1987178>
|
// <https://stackoverflow.com/a/44383330/1987178>
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
# define PRIexitcode "lu"
|
|
||||||
# ifdef _WIN64
|
# ifdef _WIN64
|
||||||
# define PRIsizet PRIu64
|
# define PRIsizet PRIu64
|
||||||
|
# define PRIexitcode "lu"
|
||||||
# else
|
# else
|
||||||
# define PRIsizet PRIu32
|
# define PRIsizet PRIu32
|
||||||
|
# define PRIexitcode "u"
|
||||||
# endif
|
# endif
|
||||||
#else
|
#else
|
||||||
# define PRIsizet "zu"
|
# define PRIsizet "zu"
|
||||||
@@ -38,7 +39,6 @@ SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code);
|
|||||||
|
|
||||||
process_t adb_execute(const char *serial, const char *const adb_cmd[], int len);
|
process_t adb_execute(const char *serial, const char *const adb_cmd[], int len);
|
||||||
process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name);
|
process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name);
|
||||||
process_t adb_forward_remove(const char *serial, uint16_t local_port);
|
|
||||||
process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port);
|
process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port);
|
||||||
process_t adb_reverse_remove(const char *serial, const char *device_socket_name);
|
process_t adb_reverse_remove(const char *serial, const char *device_socket_name);
|
||||||
process_t adb_push(const char *serial, const char *local, const char *remote);
|
process_t adb_push(const char *serial, const char *local, const char *remote);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
#include <SDL2/SDL_stdinc.h>
|
#include <SDL2/SDL_stdinc.h>
|
||||||
|
|
||||||
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
|
|
||||||
#define MIN(X,Y) (X) < (Y) ? (X) : (Y)
|
#define MIN(X,Y) (X) < (Y) ? (X) : (Y)
|
||||||
#define MAX(X,Y) (X) > (Y) ? (X) : (Y)
|
#define MAX(X,Y) (X) > (Y) ? (X) : (Y)
|
||||||
|
|
||||||
|
|||||||
@@ -36,12 +36,11 @@ int control_event_serialize(const struct control_event *event, unsigned char *bu
|
|||||||
// write length (1 byte) + date (non nul-terminated)
|
// write length (1 byte) + date (non nul-terminated)
|
||||||
size_t len = strlen(event->text_event.text);
|
size_t len = strlen(event->text_event.text);
|
||||||
if (len > TEXT_MAX_LENGTH) {
|
if (len > TEXT_MAX_LENGTH) {
|
||||||
// injecting a text takes time, so limit the text length
|
|
||||||
len = TEXT_MAX_LENGTH;
|
len = TEXT_MAX_LENGTH;
|
||||||
}
|
}
|
||||||
write16(&buf[1], (Uint16) len);
|
buf[1] = (Uint8) len;
|
||||||
memcpy(&buf[3], event->text_event.text, len);
|
memcpy(&buf[2], &event->text_event.text, len);
|
||||||
return 3 + len;
|
return 2 + len;
|
||||||
}
|
}
|
||||||
case CONTROL_EVENT_TYPE_MOUSE:
|
case CONTROL_EVENT_TYPE_MOUSE:
|
||||||
buf[1] = event->mouse_event.action;
|
buf[1] = event->mouse_event.action;
|
||||||
@@ -62,12 +61,6 @@ int control_event_serialize(const struct control_event *event, unsigned char *bu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void control_event_destroy(struct control_event *event) {
|
|
||||||
if (event->type == CONTROL_EVENT_TYPE_TEXT) {
|
|
||||||
SDL_free(event->text_event.text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_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;
|
return queue->head == queue->tail;
|
||||||
}
|
}
|
||||||
@@ -84,11 +77,7 @@ 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) {
|
||||||
int i = queue->tail;
|
// nothing to do in the current implementation
|
||||||
while (i != queue->head) {
|
|
||||||
control_event_destroy(&queue->data[i]);
|
|
||||||
i = (i + 1) % CONTROL_EVENT_QUEUE_SIZE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_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) {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#define CONTROL_EVENT_QUEUE_SIZE 64
|
#define CONTROL_EVENT_QUEUE_SIZE 64
|
||||||
#define TEXT_MAX_LENGTH 300
|
#define SERIALIZED_EVENT_MAX_SIZE 33
|
||||||
#define SERIALIZED_EVENT_MAX_SIZE 3 + TEXT_MAX_LENGTH
|
#define TEXT_MAX_LENGTH 31
|
||||||
|
|
||||||
enum control_event_type {
|
enum control_event_type {
|
||||||
CONTROL_EVENT_TYPE_KEYCODE,
|
CONTROL_EVENT_TYPE_KEYCODE,
|
||||||
@@ -20,7 +20,7 @@ enum control_event_type {
|
|||||||
CONTROL_EVENT_TYPE_COMMAND,
|
CONTROL_EVENT_TYPE_COMMAND,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define CONTROL_EVENT_COMMAND_BACK_OR_SCREEN_ON 0
|
#define CONTROL_EVENT_COMMAND_SCREEN_ON 0
|
||||||
|
|
||||||
struct control_event {
|
struct control_event {
|
||||||
enum control_event_type type;
|
enum control_event_type type;
|
||||||
@@ -31,7 +31,7 @@ struct control_event {
|
|||||||
enum android_metastate metastate;
|
enum android_metastate metastate;
|
||||||
} keycode_event;
|
} keycode_event;
|
||||||
struct {
|
struct {
|
||||||
char *text; // owned, to be freed by SDL_free()
|
char text[TEXT_MAX_LENGTH + 1]; // nul-terminated string
|
||||||
} text_event;
|
} text_event;
|
||||||
struct {
|
struct {
|
||||||
enum android_motionevent_action action;
|
enum android_motionevent_action action;
|
||||||
@@ -68,6 +68,4 @@ SDL_bool control_event_queue_is_full(const struct control_event_queue *queue);
|
|||||||
SDL_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);
|
SDL_bool control_event_queue_take(struct control_event_queue *queue, struct control_event *event);
|
||||||
|
|
||||||
void control_event_destroy(struct control_event *event);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -65,9 +65,7 @@ static int run_controller(void *data) {
|
|||||||
}
|
}
|
||||||
struct control_event event;
|
struct control_event event;
|
||||||
while (control_event_queue_take(&controller->queue, &event)) {
|
while (control_event_queue_take(&controller->queue, &event)) {
|
||||||
SDL_bool ok = process_event(controller, &event);
|
if (!process_event(controller, &event)) {
|
||||||
control_event_destroy(&event);
|
|
||||||
if (!ok) {
|
|
||||||
LOGD("Cannot write event to socket");
|
LOGD("Cannot write event to socket");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
|
|||||||
|
|
||||||
SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
|
SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
|
||||||
struct size screen_size,
|
struct size screen_size,
|
||||||
|
struct hidpi_scale *hidpi_scale,
|
||||||
struct control_event *to) {
|
struct control_event *to) {
|
||||||
to->type = CONTROL_EVENT_TYPE_MOUSE;
|
to->type = CONTROL_EVENT_TYPE_MOUSE;
|
||||||
|
|
||||||
@@ -145,21 +146,30 @@ SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
|
|||||||
|
|
||||||
to->mouse_event.buttons = convert_mouse_buttons(SDL_BUTTON(from->button));
|
to->mouse_event.buttons = convert_mouse_buttons(SDL_BUTTON(from->button));
|
||||||
to->mouse_event.position.screen_size = screen_size;
|
to->mouse_event.position.screen_size = screen_size;
|
||||||
to->mouse_event.position.point.x = from->x;
|
|
||||||
to->mouse_event.position.point.y = from->y;
|
Sint32 x = from->x;
|
||||||
|
Sint32 y = from->y;
|
||||||
|
hidpi_unscale_coordinates(hidpi_scale, &x, &y);
|
||||||
|
to->mouse_event.position.point.x = (Uint16) x;
|
||||||
|
to->mouse_event.position.point.y = (Uint16) y;
|
||||||
|
|
||||||
return SDL_TRUE;
|
return SDL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
|
SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
|
||||||
struct size screen_size,
|
struct size screen_size,
|
||||||
|
struct hidpi_scale *hidpi_scale,
|
||||||
struct control_event *to) {
|
struct control_event *to) {
|
||||||
to->type = CONTROL_EVENT_TYPE_MOUSE;
|
to->type = CONTROL_EVENT_TYPE_MOUSE;
|
||||||
to->mouse_event.action = AMOTION_EVENT_ACTION_MOVE;
|
to->mouse_event.action = AMOTION_EVENT_ACTION_MOVE;
|
||||||
to->mouse_event.buttons = convert_mouse_buttons(from->state);
|
to->mouse_event.buttons = convert_mouse_buttons(from->state);
|
||||||
to->mouse_event.position.screen_size = screen_size;
|
to->mouse_event.position.screen_size = screen_size;
|
||||||
to->mouse_event.position.point.x = from->x;
|
|
||||||
to->mouse_event.position.point.y = from->y;
|
Sint32 x = from->x;
|
||||||
|
Sint32 y = from->y;
|
||||||
|
hidpi_unscale_coordinates(hidpi_scale, &x, &y);
|
||||||
|
to->mouse_event.position.point.x = (Uint16) x;
|
||||||
|
to->mouse_event.position.point.y = (Uint16) y;
|
||||||
|
|
||||||
return SDL_TRUE;
|
return SDL_TRUE;
|
||||||
}
|
}
|
||||||
@@ -172,10 +182,7 @@ SDL_bool mouse_wheel_from_sdl_to_android(const SDL_MouseWheelEvent *from,
|
|||||||
to->scroll_event.position = position;
|
to->scroll_event.position = position;
|
||||||
|
|
||||||
int mul = from->direction == SDL_MOUSEWHEEL_NORMAL ? 1 : -1;
|
int mul = from->direction == SDL_MOUSEWHEEL_NORMAL ? 1 : -1;
|
||||||
// SDL behavior seems inconsistent between horizontal and vertical scrolling
|
to->scroll_event.hscroll = mul * from->x;
|
||||||
// so reverse the horizontal
|
|
||||||
// <https://wiki.libsdl.org/SDL_MouseWheelEvent#Remarks>
|
|
||||||
to->scroll_event.hscroll = -mul * from->x;
|
|
||||||
to->scroll_event.vscroll = mul * from->y;
|
to->scroll_event.vscroll = mul * from->y;
|
||||||
|
|
||||||
return SDL_TRUE;
|
return SDL_TRUE;
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
#include <SDL2/SDL_stdinc.h>
|
#include <SDL2/SDL_stdinc.h>
|
||||||
#include <SDL2/SDL_events.h>
|
#include <SDL2/SDL_events.h>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
#include "controlevent.h"
|
#include "controlevent.h"
|
||||||
|
#include "hidpi.h"
|
||||||
|
|
||||||
struct complete_mouse_motion_event {
|
struct complete_mouse_motion_event {
|
||||||
SDL_MouseMotionEvent *mouse_motion_event;
|
SDL_MouseMotionEvent *mouse_motion_event;
|
||||||
@@ -19,12 +22,14 @@ SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
|
|||||||
struct control_event *to);
|
struct control_event *to);
|
||||||
SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
|
SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
|
||||||
struct size screen_size,
|
struct size screen_size,
|
||||||
|
struct hidpi_scale *hidpi_scale,
|
||||||
struct control_event *to);
|
struct control_event *to);
|
||||||
|
|
||||||
// the video size may be different from the real device size, so we need the size
|
// 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
|
// to which the absolute position apply, to scale it accordingly
|
||||||
SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
|
SDL_bool mouse_motion_from_sdl_to_android(const SDL_MouseMotionEvent *from,
|
||||||
struct size screen_size,
|
struct size screen_size,
|
||||||
|
struct hidpi_scale *hidpi_scale,
|
||||||
struct control_event *to);
|
struct control_event *to);
|
||||||
|
|
||||||
// on Android, a scroll event requires the current mouse position
|
// on Android, a scroll event requires the current mouse position
|
||||||
|
|||||||
@@ -40,33 +40,37 @@ static void notify_stopped(void) {
|
|||||||
|
|
||||||
static int run_decoder(void *data) {
|
static int run_decoder(void *data) {
|
||||||
struct decoder *decoder = data;
|
struct decoder *decoder = data;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
|
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
|
||||||
if (!codec) {
|
if (!codec) {
|
||||||
LOGE("H.264 decoder not found");
|
LOGE("H.264 decoder not found");
|
||||||
goto run_end;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
|
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
|
||||||
if (!codec_ctx) {
|
if (!codec_ctx) {
|
||||||
LOGC("Could not allocate decoder context");
|
LOGC("Could not allocate decoder context");
|
||||||
goto run_end;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
|
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
|
||||||
LOGE("Could not open H.264 codec");
|
LOGE("Could not open H.264 codec");
|
||||||
|
ret = -1;
|
||||||
goto run_finally_free_codec_ctx;
|
goto run_finally_free_codec_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
AVFormatContext *format_ctx = avformat_alloc_context();
|
AVFormatContext *format_ctx = avformat_alloc_context();
|
||||||
if (!format_ctx) {
|
if (!format_ctx) {
|
||||||
LOGC("Could not allocate format context");
|
LOGC("Could not allocate format context");
|
||||||
|
ret = -1;
|
||||||
goto run_finally_close_codec;
|
goto run_finally_close_codec;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char *buffer = av_malloc(BUFSIZE);
|
unsigned char *buffer = av_malloc(BUFSIZE);
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
LOGC("Could not allocate buffer");
|
LOGC("Could not allocate buffer");
|
||||||
|
ret = -1;
|
||||||
goto run_finally_free_format_ctx;
|
goto run_finally_free_format_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +80,7 @@ static int run_decoder(void *data) {
|
|||||||
// avformat_open_input takes ownership of 'buffer'
|
// avformat_open_input takes ownership of 'buffer'
|
||||||
// so only free the buffer before avformat_open_input()
|
// so only free the buffer before avformat_open_input()
|
||||||
av_free(buffer);
|
av_free(buffer);
|
||||||
|
ret = -1;
|
||||||
goto run_finally_free_format_ctx;
|
goto run_finally_free_format_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +88,7 @@ static int run_decoder(void *data) {
|
|||||||
|
|
||||||
if (avformat_open_input(&format_ctx, NULL, NULL, NULL) < 0) {
|
if (avformat_open_input(&format_ctx, NULL, NULL, NULL) < 0) {
|
||||||
LOGE("Could not open video stream");
|
LOGE("Could not open video stream");
|
||||||
|
ret = -1;
|
||||||
goto run_finally_free_avio_ctx;
|
goto run_finally_free_avio_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,25 +97,10 @@ static int run_decoder(void *data) {
|
|||||||
packet.data = NULL;
|
packet.data = NULL;
|
||||||
packet.size = 0;
|
packet.size = 0;
|
||||||
|
|
||||||
while (!av_read_frame(format_ctx, &packet)) {
|
while (!av_read_frame(format_ctx, &packet) && !avio_ctx->eof_reached) {
|
||||||
// the new decoding/encoding API has been introduced by:
|
// the new decoding/encoding API has been introduced by:
|
||||||
// <http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=7fc329e2dd6226dfecaa4a1d7adf353bf2773726>
|
// <http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=7fc329e2dd6226dfecaa4a1d7adf353bf2773726>
|
||||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 0)
|
#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
|
|
||||||
while (packet.size > 0) {
|
while (packet.size > 0) {
|
||||||
int got_picture;
|
int got_picture;
|
||||||
int len = avcodec_decode_video2(codec_ctx, decoder->frames->decoding_frame, &got_picture, &packet);
|
int len = avcodec_decode_video2(codec_ctx, decoder->frames->decoding_frame, &got_picture, &packet);
|
||||||
@@ -123,12 +114,19 @@ static int run_decoder(void *data) {
|
|||||||
packet.size -= len;
|
packet.size -= len;
|
||||||
packet.data += len;
|
packet.data += len;
|
||||||
}
|
}
|
||||||
#endif
|
#else
|
||||||
av_packet_unref(&packet);
|
int ret;
|
||||||
|
if ((ret = avcodec_send_packet(codec_ctx, &packet)) < 0) {
|
||||||
if (avio_ctx->eof_reached) {
|
LOGE("Could not send video packet: %d", ret);
|
||||||
break;
|
goto run_quit;
|
||||||
}
|
}
|
||||||
|
if ((ret = avcodec_receive_frame(codec_ctx, decoder->frames->decoding_frame)) < 0) {
|
||||||
|
LOGE("Could not receive video frame: %d", ret);
|
||||||
|
goto run_quit;
|
||||||
|
}
|
||||||
|
|
||||||
|
push_frame(decoder);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGD("End of frames");
|
LOGD("End of frames");
|
||||||
@@ -144,8 +142,7 @@ run_finally_close_codec:
|
|||||||
run_finally_free_codec_ctx:
|
run_finally_free_codec_ctx:
|
||||||
avcodec_free_context(&codec_ctx);
|
avcodec_free_context(&codec_ctx);
|
||||||
notify_stopped();
|
notify_stopped();
|
||||||
run_end:
|
return ret;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void decoder_init(struct decoder *decoder, struct frames *frames, socket_t video_socket) {
|
void decoder_init(struct decoder *decoder, struct frames *frames, socket_t video_socket) {
|
||||||
|
|||||||
16
app/src/hidpi.c
Normal file
16
app/src/hidpi.c
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#include "hidpi.h"
|
||||||
|
|
||||||
|
void hidpi_get_scale(struct screen *screen, struct hidpi_scale *scale) {
|
||||||
|
SDL_GL_GetDrawableSize(screen->window, &scale->horizontal.num, &scale->vertical.num);
|
||||||
|
SDL_GetWindowSize(screen->window, &scale->horizontal.div, &scale->vertical.div);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hidpi_unscale_coordinates(struct hidpi_scale *scale, Sint32 *x, Sint32 *y) {
|
||||||
|
// to unscale, we devide by the ratio (so num and div are reversed)
|
||||||
|
if (scale->horizontal.num) {
|
||||||
|
*x = ((Sint64) *x) * scale->horizontal.div / scale->horizontal.num;
|
||||||
|
}
|
||||||
|
if (scale->vertical.num) {
|
||||||
|
*y = ((Sint64) *y) * scale->vertical.div / scale->vertical.num;
|
||||||
|
}
|
||||||
|
}
|
||||||
24
app/src/hidpi.h
Normal file
24
app/src/hidpi.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef HIDPI_H
|
||||||
|
#define HIDPI_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "screen.h"
|
||||||
|
|
||||||
|
// rational number p/q
|
||||||
|
struct rational {
|
||||||
|
int num;
|
||||||
|
int div;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct hidpi_scale {
|
||||||
|
struct rational horizontal; // drawable.width / window.width
|
||||||
|
struct rational vertical; // drawable.height / window.height
|
||||||
|
};
|
||||||
|
|
||||||
|
void hidpi_get_scale(struct screen *screen, struct hidpi_scale *hidpi_scale);
|
||||||
|
|
||||||
|
// mouse location need to be "unscaled" if hidpi is enabled
|
||||||
|
// <https://nlguillemot.wordpress.com/2016/12/11/high-dpi-rendering/>
|
||||||
|
void hidpi_unscale_coordinates(struct hidpi_scale *hidpi_scale, Sint32 *x, Sint32 *y);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "inputmanager.h"
|
#include "inputmanager.h"
|
||||||
|
|
||||||
#include "convert.h"
|
#include "convert.h"
|
||||||
|
#include "hidpi.h"
|
||||||
#include "lockutil.h"
|
#include "lockutil.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
@@ -18,10 +19,19 @@ static void convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int
|
|||||||
}
|
}
|
||||||
|
|
||||||
static struct point get_mouse_point(struct screen *screen) {
|
static struct point get_mouse_point(struct screen *screen) {
|
||||||
int x;
|
int mx;
|
||||||
int y;
|
int my;
|
||||||
SDL_GetMouseState(&x, &y);
|
SDL_GetMouseState(&mx, &my);
|
||||||
convert_to_renderer_coordinates(screen->renderer, &x, &y);
|
convert_to_renderer_coordinates(screen->renderer, &mx, &my);
|
||||||
|
|
||||||
|
struct hidpi_scale hidpi_scale;
|
||||||
|
hidpi_get_scale(screen, &hidpi_scale);
|
||||||
|
|
||||||
|
// SDL sometimes uses "int", sometimes "Sint32"
|
||||||
|
Sint32 x = mx;
|
||||||
|
Sint32 y = my;
|
||||||
|
hidpi_unscale_coordinates(&hidpi_scale, &x, &y);
|
||||||
|
|
||||||
SDL_assert_release(x >= 0 && x < 0x10000 && y >= 0 && y < 0x10000);
|
SDL_assert_release(x >= 0 && x < 0x10000 && y >= 0 && y < 0x10000);
|
||||||
return (struct point) {
|
return (struct point) {
|
||||||
.x = (Uint16) x,
|
.x = (Uint16) x,
|
||||||
@@ -36,11 +46,14 @@ static SDL_bool is_ctrl_down(void) {
|
|||||||
|
|
||||||
static void send_keycode(struct controller *controller, enum android_keycode keycode, const char *name) {
|
static void send_keycode(struct controller *controller, enum android_keycode keycode, const char *name) {
|
||||||
// send DOWN event
|
// send DOWN event
|
||||||
struct control_event control_event;
|
struct control_event control_event = {
|
||||||
control_event.type = CONTROL_EVENT_TYPE_KEYCODE;
|
.type = CONTROL_EVENT_TYPE_KEYCODE,
|
||||||
control_event.keycode_event.action = AKEY_EVENT_ACTION_DOWN;
|
.keycode_event = {
|
||||||
control_event.keycode_event.keycode = keycode;
|
.action = AKEY_EVENT_ACTION_DOWN,
|
||||||
control_event.keycode_event.metastate = 0;
|
.keycode = keycode,
|
||||||
|
.metastate = 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
if (!controller_push_event(controller, &control_event)) {
|
if (!controller_push_event(controller, &control_event)) {
|
||||||
LOGW("Cannot send %s (DOWN)", name);
|
LOGW("Cannot send %s (DOWN)", name);
|
||||||
@@ -78,16 +91,13 @@ static inline void action_volume_down(struct controller *controller) {
|
|||||||
send_keycode(controller, AKEYCODE_VOLUME_DOWN, "VOLUME_DOWN");
|
send_keycode(controller, AKEYCODE_VOLUME_DOWN, "VOLUME_DOWN");
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void action_menu(struct controller *controller) {
|
static void turn_screen_on(struct controller *controller) {
|
||||||
send_keycode(controller, AKEYCODE_MENU, "MENU");
|
struct control_event control_event = {
|
||||||
}
|
.type = CONTROL_EVENT_TYPE_COMMAND,
|
||||||
|
.command_event = {
|
||||||
// turn the screen on if it was off, press BACK otherwise
|
.action = CONTROL_EVENT_COMMAND_SCREEN_ON,
|
||||||
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;
|
|
||||||
|
|
||||||
if (!controller_push_event(controller, &control_event)) {
|
if (!controller_push_event(controller, &control_event)) {
|
||||||
LOGW("Cannot turn screen on");
|
LOGW("Cannot turn screen on");
|
||||||
}
|
}
|
||||||
@@ -105,27 +115,6 @@ static void switch_fps_counter_state(struct frames *frames) {
|
|||||||
mutex_unlock(frames->mutex);
|
mutex_unlock(frames->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void clipboard_paste(struct controller *controller) {
|
|
||||||
char *text = SDL_GetClipboardText();
|
|
||||||
if (!text) {
|
|
||||||
LOGW("Cannot get clipboard text: %s", SDL_GetError());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!*text) {
|
|
||||||
// empty text
|
|
||||||
SDL_free(text);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct control_event control_event;
|
|
||||||
control_event.type = CONTROL_EVENT_TYPE_TEXT;
|
|
||||||
control_event.text_event.text = text;
|
|
||||||
if (!controller_push_event(controller, &control_event)) {
|
|
||||||
SDL_free(text);
|
|
||||||
LOGW("Cannot send clipboard paste event");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void input_manager_process_text_input(struct input_manager *input_manager,
|
void input_manager_process_text_input(struct input_manager *input_manager,
|
||||||
const SDL_TextInputEvent *event) {
|
const SDL_TextInputEvent *event) {
|
||||||
if (is_ctrl_down()) {
|
if (is_ctrl_down()) {
|
||||||
@@ -142,11 +131,8 @@ void input_manager_process_text_input(struct input_manager *input_manager,
|
|||||||
|
|
||||||
struct control_event control_event;
|
struct control_event control_event;
|
||||||
control_event.type = CONTROL_EVENT_TYPE_TEXT;
|
control_event.type = CONTROL_EVENT_TYPE_TEXT;
|
||||||
control_event.text_event.text = SDL_strdup(event->text);
|
strncpy(control_event.text_event.text, event->text, TEXT_MAX_LENGTH);
|
||||||
if (!control_event.text_event.text) {
|
control_event.text_event.text[TEXT_MAX_LENGTH] = '\0';
|
||||||
LOGW("Cannot strdup input text");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!controller_push_event(input_manager->controller, &control_event)) {
|
if (!controller_push_event(input_manager->controller, &control_event)) {
|
||||||
LOGW("Cannot send text event");
|
LOGW("Cannot send text event");
|
||||||
}
|
}
|
||||||
@@ -154,24 +140,23 @@ void input_manager_process_text_input(struct input_manager *input_manager,
|
|||||||
|
|
||||||
void input_manager_process_key(struct input_manager *input_manager,
|
void input_manager_process_key(struct input_manager *input_manager,
|
||||||
const SDL_KeyboardEvent *event) {
|
const SDL_KeyboardEvent *event) {
|
||||||
|
SDL_Keycode keycode = event->keysym.sym;
|
||||||
SDL_bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
|
SDL_bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
|
||||||
|
SDL_bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
|
||||||
|
SDL_bool repeat = event->repeat;
|
||||||
|
|
||||||
// capture all Ctrl events
|
// capture all Ctrl events
|
||||||
if (ctrl) {
|
if (ctrl) {
|
||||||
SDL_bool repeat = event->repeat;
|
|
||||||
|
|
||||||
// only consider keydown events, and ignore repeated events
|
// only consider keydown events, and ignore repeated events
|
||||||
if (repeat || event->type != SDL_KEYDOWN) {
|
if (repeat || event->type != SDL_KEYDOWN) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
|
|
||||||
if (shift) {
|
if (shift) {
|
||||||
// currently, there is no shortcut implying SHIFT
|
// currently, there is no shortcut implying SHIFT
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Keycode keycode = event->keysym.sym;
|
|
||||||
switch (keycode) {
|
switch (keycode) {
|
||||||
case SDLK_h:
|
case SDLK_h:
|
||||||
action_home(input_manager->controller);
|
action_home(input_manager->controller);
|
||||||
@@ -180,18 +165,12 @@ void input_manager_process_key(struct input_manager *input_manager,
|
|||||||
case SDLK_BACKSPACE:
|
case SDLK_BACKSPACE:
|
||||||
action_back(input_manager->controller);
|
action_back(input_manager->controller);
|
||||||
return;
|
return;
|
||||||
case SDLK_s:
|
|
||||||
action_app_switch(input_manager->controller);
|
|
||||||
return;
|
|
||||||
case SDLK_m:
|
case SDLK_m:
|
||||||
action_menu(input_manager->controller);
|
action_app_switch(input_manager->controller);
|
||||||
return;
|
return;
|
||||||
case SDLK_p:
|
case SDLK_p:
|
||||||
action_power(input_manager->controller);
|
action_power(input_manager->controller);
|
||||||
return;
|
return;
|
||||||
case SDLK_v:
|
|
||||||
clipboard_paste(input_manager->controller);
|
|
||||||
return;
|
|
||||||
case SDLK_f:
|
case SDLK_f:
|
||||||
screen_switch_fullscreen(input_manager->screen);
|
screen_switch_fullscreen(input_manager->screen);
|
||||||
return;
|
return;
|
||||||
@@ -223,8 +202,12 @@ void input_manager_process_mouse_motion(struct input_manager *input_manager,
|
|||||||
// do not send motion events when no button is pressed
|
// do not send motion events when no button is pressed
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct hidpi_scale hidpi_scale;
|
||||||
|
hidpi_get_scale(input_manager->screen, &hidpi_scale);
|
||||||
|
|
||||||
struct control_event control_event;
|
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, &hidpi_scale, &control_event)) {
|
||||||
if (!controller_push_event(input_manager->controller, &control_event)) {
|
if (!controller_push_event(input_manager->controller, &control_event)) {
|
||||||
LOGW("Cannot send mouse motion event");
|
LOGW("Cannot send mouse motion event");
|
||||||
}
|
}
|
||||||
@@ -233,29 +216,16 @@ void input_manager_process_mouse_motion(struct input_manager *input_manager,
|
|||||||
|
|
||||||
void input_manager_process_mouse_button(struct input_manager *input_manager,
|
void input_manager_process_mouse_button(struct input_manager *input_manager,
|
||||||
const SDL_MouseButtonEvent *event) {
|
const SDL_MouseButtonEvent *event) {
|
||||||
if (event->type == SDL_MOUSEBUTTONDOWN) {
|
if (event->button == SDL_BUTTON_RIGHT && event->type == SDL_MOUSEBUTTONDOWN) {
|
||||||
if (event->button == SDL_BUTTON_RIGHT) {
|
turn_screen_on(input_manager->controller);
|
||||||
press_back_or_turn_screen_on(input_manager->controller);
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event->button == SDL_BUTTON_MIDDLE) {
|
|
||||||
action_home(input_manager->controller);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// double-click on black borders resize to fit the device screen
|
|
||||||
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct hidpi_scale hidpi_scale;
|
||||||
|
hidpi_get_scale(input_manager->screen, &hidpi_scale);
|
||||||
|
|
||||||
struct control_event control_event;
|
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, &hidpi_scale, &control_event)) {
|
||||||
if (!controller_push_event(input_manager->controller, &control_event)) {
|
if (!controller_push_event(input_manager->controller, &control_event)) {
|
||||||
LOGW("Cannot send mouse button event");
|
LOGW("Cannot send mouse button event");
|
||||||
}
|
}
|
||||||
@@ -271,7 +241,7 @@ void input_manager_process_mouse_wheel(struct input_manager *input_manager,
|
|||||||
struct control_event control_event;
|
struct control_event control_event;
|
||||||
if (mouse_wheel_from_sdl_to_android(event, position, &control_event)) {
|
if (mouse_wheel_from_sdl_to_android(event, position, &control_event)) {
|
||||||
if (!controller_push_event(input_manager->controller, &control_event)) {
|
if (!controller_push_event(input_manager->controller, &control_event)) {
|
||||||
LOGW("Cannot send mouse wheel event");
|
LOGW("Cannot send wheel button event");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ struct args {
|
|||||||
const char *serial;
|
const char *serial;
|
||||||
SDL_bool help;
|
SDL_bool help;
|
||||||
SDL_bool version;
|
SDL_bool version;
|
||||||
SDL_bool show_touches;
|
|
||||||
Uint16 port;
|
Uint16 port;
|
||||||
Uint16 max_size;
|
Uint16 max_size;
|
||||||
Uint32 bit_rate;
|
Uint32 bit_rate;
|
||||||
@@ -46,10 +45,6 @@ static void usage(const char *arg0) {
|
|||||||
" The device serial number. Mandatory only if several devices\n"
|
" The device serial number. Mandatory only if several devices\n"
|
||||||
" are connected to adb.\n"
|
" are connected to adb.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -t, --show-touches\n"
|
|
||||||
" Enable \"show touches\" on start, disable on quit.\n"
|
|
||||||
" It only shows physical touches (not clicks from scrcpy).\n"
|
|
||||||
"\n"
|
|
||||||
" -v, --version\n"
|
" -v, --version\n"
|
||||||
" Print the version of scrcpy.\n"
|
" Print the version of scrcpy.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -62,24 +57,18 @@ static void usage(const char *arg0) {
|
|||||||
" resize window to 1:1 (pixel-perfect)\n"
|
" resize window to 1:1 (pixel-perfect)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Ctrl+x\n"
|
" Ctrl+x\n"
|
||||||
" Double-click on black borders\n"
|
|
||||||
" resize window to remove black borders\n"
|
" resize window to remove black borders\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Ctrl+h\n"
|
" Ctrl+h\n"
|
||||||
" Home\n"
|
" Home\n"
|
||||||
" Middle-click\n"
|
|
||||||
" click on HOME\n"
|
" click on HOME\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Ctrl+b\n"
|
" Ctrl+b\n"
|
||||||
" Ctrl+Backspace\n"
|
" Ctrl+Backspace\n"
|
||||||
" Right-click (when screen is on)\n"
|
|
||||||
" click on BACK\n"
|
" click on BACK\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Ctrl+s\n"
|
|
||||||
" click on APP_SWITCH\n"
|
|
||||||
"\n"
|
|
||||||
" Ctrl+m\n"
|
" Ctrl+m\n"
|
||||||
" click on MENU\n"
|
" click on APP_SWITCH\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Ctrl+'+'\n"
|
" Ctrl+'+'\n"
|
||||||
" click on VOLUME_UP\n"
|
" click on VOLUME_UP\n"
|
||||||
@@ -90,12 +79,9 @@ static void usage(const char *arg0) {
|
|||||||
" Ctrl+p\n"
|
" Ctrl+p\n"
|
||||||
" click on POWER (turn screen on/off)\n"
|
" click on POWER (turn screen on/off)\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Right-click (when screen is off)\n"
|
" Right-click\n"
|
||||||
" turn screen on\n"
|
" turn screen on\n"
|
||||||
"\n"
|
"\n"
|
||||||
" Ctrl+v\n"
|
|
||||||
" paste computer clipboard to device\n"
|
|
||||||
"\n"
|
|
||||||
" Ctrl+i\n"
|
" Ctrl+i\n"
|
||||||
" enable/disable FPS counter (print frames/second in logs)\n"
|
" enable/disable FPS counter (print frames/second in logs)\n"
|
||||||
"\n",
|
"\n",
|
||||||
@@ -188,45 +174,47 @@ static SDL_bool parse_port(char *optarg, Uint16 *port) {
|
|||||||
|
|
||||||
static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
|
static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
|
||||||
static const struct option long_options[] = {
|
static const struct option long_options[] = {
|
||||||
{"bit-rate", required_argument, NULL, 'b'},
|
{"bit-rate", required_argument, NULL, 'b'},
|
||||||
{"help", no_argument, NULL, 'h'},
|
{"help", no_argument, NULL, 'h'},
|
||||||
{"max-size", required_argument, NULL, 'm'},
|
{"max-size", required_argument, NULL, 'm'},
|
||||||
{"port", required_argument, NULL, 'p'},
|
{"port", required_argument, NULL, 'p'},
|
||||||
{"serial", required_argument, NULL, 's'},
|
{"serial", required_argument, NULL, 's'},
|
||||||
{"show-touches", no_argument, NULL, 't'},
|
{"version", no_argument, NULL, 'v'},
|
||||||
{"version", no_argument, NULL, 'v'},
|
{NULL, 0, NULL, 0 },
|
||||||
{NULL, 0, NULL, 0 },
|
|
||||||
};
|
};
|
||||||
int c;
|
int c;
|
||||||
while ((c = getopt_long(argc, argv, "b:hm:p:s:tv", long_options, NULL)) != -1) {
|
while ((c = getopt_long(argc, argv, "b:hm:p:s:v", long_options, NULL)) != -1) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'b':
|
case 'b': {
|
||||||
if (!parse_bit_rate(optarg, &args->bit_rate)) {
|
if (!parse_bit_rate(optarg, &args->bit_rate)) {
|
||||||
return SDL_FALSE;
|
return SDL_FALSE;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'h':
|
}
|
||||||
|
case 'h': {
|
||||||
args->help = SDL_TRUE;
|
args->help = SDL_TRUE;
|
||||||
break;
|
break;
|
||||||
case 'm':
|
}
|
||||||
|
case 'm': {
|
||||||
if (!parse_max_size(optarg, &args->max_size)) {
|
if (!parse_max_size(optarg, &args->max_size)) {
|
||||||
return SDL_FALSE;
|
return SDL_FALSE;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'p':
|
}
|
||||||
|
case 'p': {
|
||||||
if (!parse_port(optarg, &args->port)) {
|
if (!parse_port(optarg, &args->port)) {
|
||||||
return SDL_FALSE;
|
return SDL_FALSE;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 's':
|
}
|
||||||
|
case 's': {
|
||||||
args->serial = optarg;
|
args->serial = optarg;
|
||||||
break;
|
break;
|
||||||
case 't':
|
}
|
||||||
args->show_touches = SDL_TRUE;
|
case 'v': {
|
||||||
break;
|
|
||||||
case 'v':
|
|
||||||
args->version = SDL_TRUE;
|
args->version = SDL_TRUE;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return SDL_FALSE;
|
return SDL_FALSE;
|
||||||
@@ -242,17 +230,10 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
setbuf(stdout, NULL);
|
|
||||||
setbuf(stderr, NULL);
|
|
||||||
#endif
|
|
||||||
struct args args = {
|
struct args args = {
|
||||||
.serial = NULL,
|
.serial = NULL,
|
||||||
.help = SDL_FALSE,
|
.help = SDL_FALSE,
|
||||||
.version = SDL_FALSE,
|
.version = SDL_FALSE,
|
||||||
.show_touches = SDL_FALSE,
|
|
||||||
.port = DEFAULT_LOCAL_PORT,
|
.port = DEFAULT_LOCAL_PORT,
|
||||||
.max_size = DEFAULT_MAX_SIZE,
|
.max_size = DEFAULT_MAX_SIZE,
|
||||||
.bit_rate = DEFAULT_BIT_RATE,
|
.bit_rate = DEFAULT_BIT_RATE,
|
||||||
@@ -281,14 +262,7 @@ int main(int argc, char *argv[]) {
|
|||||||
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
|
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct scrcpy_options options = {
|
int res = scrcpy(args.serial, args.port, args.max_size, args.bit_rate) ? 0 : 1;
|
||||||
.serial = args.serial,
|
|
||||||
.port = args.port,
|
|
||||||
.max_size = args.max_size,
|
|
||||||
.bit_rate = args.bit_rate,
|
|
||||||
.show_touches = args.show_touches,
|
|
||||||
};
|
|
||||||
int res = scrcpy(&options) ? 0 : 1;
|
|
||||||
|
|
||||||
avformat_network_deinit(); // ignore failure
|
avformat_network_deinit(); // ignore failure
|
||||||
|
|
||||||
|
|||||||
@@ -18,26 +18,6 @@
|
|||||||
typedef struct in_addr IN_ADDR;
|
typedef struct in_addr IN_ADDR;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
socket_t net_connect(Uint32 addr, Uint16 port) {
|
|
||||||
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
if (sock == INVALID_SOCKET) {
|
|
||||||
perror("socket");
|
|
||||||
return INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
SOCKADDR_IN sin;
|
|
||||||
sin.sin_family = AF_INET;
|
|
||||||
sin.sin_addr.s_addr = htonl(addr);
|
|
||||||
sin.sin_port = htons(port);
|
|
||||||
|
|
||||||
if (connect(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
|
|
||||||
perror("connect");
|
|
||||||
return INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sock;
|
|
||||||
}
|
|
||||||
|
|
||||||
socket_t net_listen(Uint32 addr, Uint16 port, int backlog) {
|
socket_t net_listen(Uint32 addr, Uint16 port, int backlog) {
|
||||||
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
if (sock == INVALID_SOCKET) {
|
if (sock == INVALID_SOCKET) {
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#define SHUT_RD SD_RECEIVE
|
#define SHUT_RD SD_RECEIVE
|
||||||
#define SHUT_WR SD_SEND
|
#define SHUT_WR SD_SEND
|
||||||
#define SHUT_RDWR SD_BOTH
|
#define SHUT_RDWR SD_BOTH
|
||||||
|
typedef SIZE_T size_t;
|
||||||
|
typedef SSIZE_T ssize_t;
|
||||||
typedef SOCKET socket_t;
|
typedef SOCKET socket_t;
|
||||||
#else
|
#else
|
||||||
# include <sys/socket.h>
|
# include <sys/socket.h>
|
||||||
@@ -19,7 +21,6 @@
|
|||||||
SDL_bool net_init(void);
|
SDL_bool net_init(void);
|
||||||
void net_cleanup(void);
|
void net_cleanup(void);
|
||||||
|
|
||||||
socket_t net_connect(Uint32 addr, Uint16 port);
|
|
||||||
socket_t net_listen(Uint32 addr, Uint16 port, int backlog);
|
socket_t net_listen(Uint32 addr, Uint16 port, int backlog);
|
||||||
socket_t net_accept(socket_t server_socket);
|
socket_t net_accept(socket_t server_socket);
|
||||||
|
|
||||||
|
|||||||
103
app/src/scrcpy.c
103
app/src/scrcpy.c
@@ -35,29 +35,7 @@ static struct input_manager input_manager = {
|
|||||||
.screen = &screen,
|
.screen = &screen,
|
||||||
};
|
};
|
||||||
|
|
||||||
#if defined(__APPLE__) || defined(__WINDOWS__)
|
|
||||||
# define CONTINUOUS_RESIZING_WORKAROUND
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
|
||||||
// On Windows and MacOS, resizing blocks the event loop, so resizing events are
|
|
||||||
// not triggered. As a workaround, handle them in an event handler.
|
|
||||||
//
|
|
||||||
// <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) {
|
|
||||||
// called from another thread, not very safe, but it's a workaround!
|
|
||||||
screen_render(&screen);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void event_loop(void) {
|
static void event_loop(void) {
|
||||||
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
|
||||||
SDL_AddEventWatch(event_watcher, NULL);
|
|
||||||
#endif
|
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while (SDL_WaitEvent(&event)) {
|
while (SDL_WaitEvent(&event)) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
@@ -79,15 +57,16 @@ static void event_loop(void) {
|
|||||||
break;
|
break;
|
||||||
case SDL_WINDOWEVENT:
|
case SDL_WINDOWEVENT:
|
||||||
switch (event.window.event) {
|
switch (event.window.event) {
|
||||||
case SDL_WINDOWEVENT_EXPOSED:
|
case SDL_WINDOWEVENT_EXPOSED:
|
||||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||||
screen_render(&screen);
|
screen_render(&screen);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SDL_TEXTINPUT:
|
case SDL_TEXTINPUT: {
|
||||||
input_manager_process_text_input(&input_manager, &event.text);
|
input_manager_process_text_input(&input_manager, &event.text);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case SDL_KEYDOWN:
|
case SDL_KEYDOWN:
|
||||||
case SDL_KEYUP:
|
case SDL_KEYUP:
|
||||||
input_manager_process_key(&input_manager, &event.key);
|
input_manager_process_key(&input_manager, &event.key);
|
||||||
@@ -95,58 +74,38 @@ static void event_loop(void) {
|
|||||||
case SDL_MOUSEMOTION:
|
case SDL_MOUSEMOTION:
|
||||||
input_manager_process_mouse_motion(&input_manager, &event.motion);
|
input_manager_process_mouse_motion(&input_manager, &event.motion);
|
||||||
break;
|
break;
|
||||||
case SDL_MOUSEWHEEL:
|
case SDL_MOUSEWHEEL: {
|
||||||
input_manager_process_mouse_wheel(&input_manager, &event.wheel);
|
input_manager_process_mouse_wheel(&input_manager, &event.wheel);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case SDL_MOUSEBUTTONDOWN:
|
case SDL_MOUSEBUTTONDOWN:
|
||||||
case SDL_MOUSEBUTTONUP:
|
case SDL_MOUSEBUTTONUP: {
|
||||||
input_manager_process_mouse_button(&input_manager, &event.button);
|
input_manager_process_mouse_button(&input_manager, &event.button);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static process_t set_show_touches_enabled(const char *serial, SDL_bool enabled) {
|
SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 bit_rate) {
|
||||||
const char *value = enabled ? "1" : "0";
|
if (!server_start(&server, serial, local_port, max_size, bit_rate)) {
|
||||||
const char *const adb_cmd[] = {
|
|
||||||
"shell", "settings", "put", "system", "show_touches", value
|
|
||||||
};
|
|
||||||
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void wait_show_touches(process_t process) {
|
|
||||||
// reap the process, ignore the result
|
|
||||||
process_check_success(process, "show_touches");
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_bool scrcpy(const struct scrcpy_options *options) {
|
|
||||||
if (!server_start(&server, options->serial, options->port,
|
|
||||||
options->max_size, options->bit_rate)) {
|
|
||||||
return SDL_FALSE;
|
return SDL_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
process_t proc_show_touches;
|
|
||||||
SDL_bool show_touches_waited;
|
|
||||||
if (options->show_touches) {
|
|
||||||
LOGI("Enable show_touches");
|
|
||||||
proc_show_touches = set_show_touches_enabled(options->serial, SDL_TRUE);
|
|
||||||
show_touches_waited = SDL_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_bool ret = SDL_TRUE;
|
SDL_bool ret = SDL_TRUE;
|
||||||
|
|
||||||
if (!SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1")) {
|
|
||||||
LOGW("Cannot request to keep default signal handlers");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sdl_init_and_configure()) {
|
if (!sdl_init_and_configure()) {
|
||||||
ret = SDL_FALSE;
|
ret = SDL_FALSE;
|
||||||
goto finally_destroy_server;
|
goto finally_destroy_server;
|
||||||
}
|
}
|
||||||
|
|
||||||
socket_t device_socket = server_connect_to(&server);
|
// SDL initialization replace the signal handler for SIGTERM, so Ctrl+C is
|
||||||
|
// managed by the event loop. This blocking call blocks the event loop, so
|
||||||
|
// timeout the connection not to block indefinitely in case of SIGTERM.
|
||||||
|
#define SERVER_CONNECT_TIMEOUT_MS 2000
|
||||||
|
socket_t device_socket = server_connect_to(&server, serial, SERVER_CONNECT_TIMEOUT_MS);
|
||||||
if (device_socket == INVALID_SOCKET) {
|
if (device_socket == INVALID_SOCKET) {
|
||||||
server_stop(&server);
|
server_stop(&server, serial);
|
||||||
ret = SDL_FALSE;
|
ret = SDL_FALSE;
|
||||||
goto finally_destroy_server;
|
goto finally_destroy_server;
|
||||||
}
|
}
|
||||||
@@ -158,13 +117,13 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
|
|||||||
// therefore, we transmit the screen size before the video stream, to be able
|
// therefore, we transmit the screen size before the video stream, to be able
|
||||||
// to init the window immediately
|
// to init the window immediately
|
||||||
if (!device_read_info(device_socket, device_name, &frame_size)) {
|
if (!device_read_info(device_socket, device_name, &frame_size)) {
|
||||||
server_stop(&server);
|
server_stop(&server, serial);
|
||||||
ret = SDL_FALSE;
|
ret = SDL_FALSE;
|
||||||
goto finally_destroy_server;
|
goto finally_destroy_server;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!frames_init(&frames)) {
|
if (!frames_init(&frames)) {
|
||||||
server_stop(&server);
|
server_stop(&server, serial);
|
||||||
ret = SDL_FALSE;
|
ret = SDL_FALSE;
|
||||||
goto finally_destroy_server;
|
goto finally_destroy_server;
|
||||||
}
|
}
|
||||||
@@ -175,7 +134,7 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
|
|||||||
// start the decoder
|
// start the decoder
|
||||||
if (!decoder_start(&decoder)) {
|
if (!decoder_start(&decoder)) {
|
||||||
ret = SDL_FALSE;
|
ret = SDL_FALSE;
|
||||||
server_stop(&server);
|
server_stop(&server, serial);
|
||||||
goto finally_destroy_frames;
|
goto finally_destroy_frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,16 +153,10 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
|
|||||||
goto finally_stop_and_join_controller;
|
goto finally_stop_and_join_controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options->show_touches) {
|
|
||||||
wait_show_touches(proc_show_touches);
|
|
||||||
show_touches_waited = SDL_TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
event_loop();
|
event_loop();
|
||||||
|
|
||||||
LOGD("quit...");
|
LOGD("quit...");
|
||||||
|
|
||||||
screen_destroy(&screen);
|
screen_destroy(&screen);
|
||||||
|
|
||||||
finally_stop_and_join_controller:
|
finally_stop_and_join_controller:
|
||||||
controller_stop(&controller);
|
controller_stop(&controller);
|
||||||
controller_join(&controller);
|
controller_join(&controller);
|
||||||
@@ -212,21 +165,11 @@ finally_destroy_controller:
|
|||||||
finally_stop_decoder:
|
finally_stop_decoder:
|
||||||
decoder_stop(&decoder);
|
decoder_stop(&decoder);
|
||||||
// stop the server before decoder_join() to wake up the decoder
|
// stop the server before decoder_join() to wake up the decoder
|
||||||
server_stop(&server);
|
server_stop(&server, serial);
|
||||||
decoder_join(&decoder);
|
decoder_join(&decoder);
|
||||||
finally_destroy_frames:
|
finally_destroy_frames:
|
||||||
frames_destroy(&frames);
|
frames_destroy(&frames);
|
||||||
finally_destroy_server:
|
finally_destroy_server:
|
||||||
if (options->show_touches) {
|
|
||||||
if (!show_touches_waited) {
|
|
||||||
// wait the process which enabled "show touches"
|
|
||||||
wait_show_touches(proc_show_touches);
|
|
||||||
}
|
|
||||||
LOGI("Disable show_touches");
|
|
||||||
proc_show_touches = set_show_touches_enabled(options->serial, SDL_FALSE);
|
|
||||||
wait_show_touches(proc_show_touches);
|
|
||||||
}
|
|
||||||
|
|
||||||
server_destroy(&server);
|
server_destroy(&server);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|||||||
@@ -3,14 +3,6 @@
|
|||||||
|
|
||||||
#include <SDL2/SDL_stdinc.h>
|
#include <SDL2/SDL_stdinc.h>
|
||||||
|
|
||||||
struct scrcpy_options {
|
SDL_bool scrcpy(const char *serial, Uint16 local_port, Uint16 max_size, Uint32 bit_rate);
|
||||||
const char *serial;
|
|
||||||
Uint16 port;
|
|
||||||
Uint16 max_size;
|
|
||||||
Uint32 bit_rate;
|
|
||||||
SDL_bool show_touches;
|
|
||||||
};
|
|
||||||
|
|
||||||
SDL_bool scrcpy(const struct scrcpy_options *options);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ SDL_bool sdl_init_and_configure(void) {
|
|||||||
|
|
||||||
atexit(SDL_Quit);
|
atexit(SDL_Quit);
|
||||||
|
|
||||||
// Use the best available scale quality
|
// Bilinear resizing
|
||||||
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2")) {
|
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) {
|
||||||
LOGW("Could not enable bilinear filtering");
|
LOGW("Could not enable bilinear filtering");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,21 +136,13 @@ void screen_init(struct screen *screen) {
|
|||||||
*screen = (struct screen) SCREEN_INITIALIZER;
|
*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,
|
|
||||||
frame_size.width, frame_size.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_bool screen_init_rendering(struct screen *screen, const char *device_name, struct size frame_size) {
|
SDL_bool screen_init_rendering(struct screen *screen, const char *device_name, struct size frame_size) {
|
||||||
screen->frame_size = frame_size;
|
screen->frame_size = frame_size;
|
||||||
|
|
||||||
struct size window_size = get_initial_optimal_size(frame_size);
|
struct size window_size = get_initial_optimal_size(frame_size);
|
||||||
Uint32 window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
|
|
||||||
#ifdef HIDPI_SUPPORT
|
|
||||||
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
|
|
||||||
#endif
|
|
||||||
screen->window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
screen->window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
||||||
window_size.width, window_size.height, window_flags);
|
window_size.width, window_size.height,
|
||||||
|
SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||||
if (!screen->window) {
|
if (!screen->window) {
|
||||||
LOGC("Could not create window: %s", SDL_GetError());
|
LOGC("Could not create window: %s", SDL_GetError());
|
||||||
return SDL_FALSE;
|
return SDL_FALSE;
|
||||||
@@ -179,7 +171,8 @@ SDL_bool screen_init_rendering(struct screen *screen, const char *device_name, s
|
|||||||
SDL_FreeSurface(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);
|
screen->texture = SDL_CreateTexture(screen->renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING,
|
||||||
|
frame_size.width, frame_size.height);
|
||||||
if (!screen->texture) {
|
if (!screen->texture) {
|
||||||
LOGC("Could not create texture: %s", SDL_GetError());
|
LOGC("Could not create texture: %s", SDL_GetError());
|
||||||
screen_destroy(screen);
|
screen_destroy(screen);
|
||||||
@@ -228,7 +221,8 @@ static SDL_bool prepare_for_frame(struct screen *screen, struct size new_frame_s
|
|||||||
|
|
||||||
LOGD("New texture: %" PRIu16 "x%" PRIu16,
|
LOGD("New texture: %" PRIu16 "x%" PRIu16,
|
||||||
screen->frame_size.width, screen->frame_size.height);
|
screen->frame_size.width, screen->frame_size.height);
|
||||||
screen->texture = create_texture(screen->renderer, new_frame_size);
|
screen->texture = SDL_CreateTexture(screen->renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING,
|
||||||
|
new_frame_size.width, new_frame_size.height);
|
||||||
if (!screen->texture) {
|
if (!screen->texture) {
|
||||||
LOGC("Could not create texture: %s", SDL_GetError());
|
LOGC("Could not create texture: %s", SDL_GetError());
|
||||||
return SDL_FALSE;
|
return SDL_FALSE;
|
||||||
|
|||||||
144
app/src/server.c
144
app/src/server.c
@@ -4,7 +4,6 @@
|
|||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <SDL2/SDL_assert.h>
|
#include <SDL2/SDL_assert.h>
|
||||||
#include <SDL2/SDL_timer.h>
|
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
@@ -38,45 +37,17 @@ static SDL_bool remove_server(const char *serial) {
|
|||||||
return process_check_success(process, "adb shell rm");
|
return process_check_success(process, "adb shell rm");
|
||||||
}
|
}
|
||||||
|
|
||||||
static SDL_bool enable_tunnel_reverse(const char *serial, Uint16 local_port) {
|
static SDL_bool enable_tunnel(const char *serial, Uint16 local_port) {
|
||||||
process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
|
process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
|
||||||
return process_check_success(process, "adb reverse");
|
return process_check_success(process, "adb reverse");
|
||||||
}
|
}
|
||||||
|
|
||||||
static SDL_bool disable_tunnel_reverse(const char *serial) {
|
static SDL_bool disable_tunnel(const char *serial) {
|
||||||
process_t process = adb_reverse_remove(serial, SOCKET_NAME);
|
process_t process = adb_reverse_remove(serial, SOCKET_NAME);
|
||||||
return process_check_success(process, "adb reverse --remove");
|
return process_check_success(process, "adb reverse --remove");
|
||||||
}
|
}
|
||||||
|
|
||||||
static SDL_bool enable_tunnel_forward(const char *serial, Uint16 local_port) {
|
static process_t execute_server(const char *serial, Uint16 max_size, Uint32 bit_rate) {
|
||||||
process_t process = adb_forward(serial, local_port, SOCKET_NAME);
|
|
||||||
return process_check_success(process, "adb forward");
|
|
||||||
}
|
|
||||||
|
|
||||||
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 SDL_bool enable_tunnel(struct server *server) {
|
|
||||||
if (enable_tunnel_reverse(server->serial, server->local_port)) {
|
|
||||||
return SDL_TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGW("'adb reverse' failed, fallback to 'adb forward'");
|
|
||||||
server->tunnel_forward = SDL_TRUE;
|
|
||||||
return enable_tunnel_forward(server->serial, server->local_port);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 max_size, Uint32 bit_rate, SDL_bool tunnel_forward) {
|
|
||||||
char max_size_string[6];
|
char max_size_string[6];
|
||||||
char bit_rate_string[11];
|
char bit_rate_string[11];
|
||||||
sprintf(max_size_string, "%"PRIu16, max_size);
|
sprintf(max_size_string, "%"PRIu16, max_size);
|
||||||
@@ -89,48 +60,15 @@ static process_t execute_server(const char *serial,
|
|||||||
"com.genymobile.scrcpy.Server",
|
"com.genymobile.scrcpy.Server",
|
||||||
max_size_string,
|
max_size_string,
|
||||||
bit_rate_string,
|
bit_rate_string,
|
||||||
tunnel_forward ? "true" : "false",
|
|
||||||
};
|
};
|
||||||
return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
|
return adb_execute(serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#define IPV4_LOCALHOST 0x7F000001
|
|
||||||
|
|
||||||
static socket_t listen_on_port(Uint16 port) {
|
static socket_t listen_on_port(Uint16 port) {
|
||||||
|
#define IPV4_LOCALHOST 0x7F000001
|
||||||
return net_listen(IPV4_LOCALHOST, port, 1);
|
return net_listen(IPV4_LOCALHOST, port, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static socket_t connect_and_read_byte(Uint16 port) {
|
|
||||||
socket_t socket = net_connect(IPV4_LOCALHOST, port);
|
|
||||||
if (socket == INVALID_SOCKET) {
|
|
||||||
return INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
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_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 port, Uint32 attempts, Uint32 delay) {
|
|
||||||
do {
|
|
||||||
LOGD("Remaining connection attempts: %d", (int) attempts);
|
|
||||||
socket_t socket = connect_and_read_byte(port);
|
|
||||||
if (socket != INVALID_SOCKET) {
|
|
||||||
// it worked!
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
if (attempts) {
|
|
||||||
SDL_Delay(delay);
|
|
||||||
}
|
|
||||||
} while (--attempts > 0);
|
|
||||||
return INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void close_socket(socket_t *socket) {
|
static void close_socket(socket_t *socket) {
|
||||||
SDL_assert(*socket != INVALID_SOCKET);
|
SDL_assert(*socket != INVALID_SOCKET);
|
||||||
net_shutdown(*socket, SHUT_RDWR);
|
net_shutdown(*socket, SHUT_RDWR);
|
||||||
@@ -147,84 +85,63 @@ void server_init(struct server *server) {
|
|||||||
|
|
||||||
SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port,
|
SDL_bool server_start(struct server *server, const char *serial, Uint16 local_port,
|
||||||
Uint16 max_size, Uint32 bit_rate) {
|
Uint16 max_size, Uint32 bit_rate) {
|
||||||
server->local_port = local_port;
|
|
||||||
|
|
||||||
if (serial) {
|
|
||||||
server->serial = SDL_strdup(serial);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!push_server(serial)) {
|
if (!push_server(serial)) {
|
||||||
return SDL_FALSE;
|
return SDL_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
server->server_copied_to_device = SDL_TRUE;
|
server->server_copied_to_device = SDL_TRUE;
|
||||||
|
|
||||||
if (!enable_tunnel(server)) {
|
if (!enable_tunnel(serial, local_port)) {
|
||||||
return SDL_FALSE;
|
return SDL_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to
|
// At the application level, the device part is "the server" because it
|
||||||
// "adb forward", so the app socket is the client
|
// serves video stream and control. However, at the network level, the
|
||||||
if (!server->tunnel_forward) {
|
// client listens and the server connects to the client. That way, the
|
||||||
// At the application level, the device part is "the server" because it
|
// client can listen before starting the server app, so there is no need to
|
||||||
// serves video stream and control. However, at the network level, the
|
// try to connect until the server socket is listening on the device.
|
||||||
// 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.
|
|
||||||
|
|
||||||
server->server_socket = listen_on_port(local_port);
|
server->server_socket = listen_on_port(local_port);
|
||||||
if (server->server_socket == INVALID_SOCKET) {
|
if (server->server_socket == INVALID_SOCKET) {
|
||||||
LOGE("Could not listen on port %" PRIu16, local_port);
|
LOGE("Could not listen on port %" PRIu16, local_port);
|
||||||
disable_tunnel(server);
|
disable_tunnel(serial);
|
||||||
return SDL_FALSE;
|
return SDL_FALSE;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// server will connect to our server socket
|
// server will connect to our server socket
|
||||||
server->process = execute_server(serial, max_size, bit_rate, server->tunnel_forward);
|
server->process = execute_server(serial, max_size, bit_rate);
|
||||||
if (server->process == PROCESS_NONE) {
|
if (server->process == PROCESS_NONE) {
|
||||||
if (!server->tunnel_forward) {
|
close_socket(&server->server_socket);
|
||||||
close_socket(&server->server_socket);
|
disable_tunnel(serial);
|
||||||
}
|
|
||||||
disable_tunnel(server);
|
|
||||||
return SDL_FALSE;
|
return SDL_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
server->tunnel_enabled = SDL_TRUE;
|
server->adb_reverse_enabled = SDL_TRUE;
|
||||||
|
|
||||||
return SDL_TRUE;
|
return SDL_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
socket_t server_connect_to(struct server *server) {
|
socket_t server_connect_to(struct server *server, const char *serial, Uint32 timeout_ms) {
|
||||||
if (!server->tunnel_forward) {
|
server->device_socket = net_accept(server->server_socket);
|
||||||
server->device_socket = net_accept(server->server_socket);
|
|
||||||
} else {
|
|
||||||
Uint32 attempts = 50;
|
|
||||||
Uint32 delay = 100; // ms
|
|
||||||
server->device_socket = connect_to_server(server->local_port, attempts, delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (server->device_socket == INVALID_SOCKET) {
|
if (server->device_socket == INVALID_SOCKET) {
|
||||||
return INVALID_SOCKET;
|
return INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!server->tunnel_forward) {
|
// we don't need the server socket anymore
|
||||||
// we don't need the server socket anymore
|
close_socket(&server->server_socket);
|
||||||
close_socket(&server->server_socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
// the server is started, we can clean up the jar from the temporary folder
|
// the server is started, we can clean up the jar from the temporary folder
|
||||||
remove_server(server->serial); // ignore failure
|
remove_server(serial); // ignore failure
|
||||||
server->server_copied_to_device = SDL_FALSE;
|
server->server_copied_to_device = SDL_FALSE;
|
||||||
|
|
||||||
// we don't need the adb tunnel anymore
|
// we don't need the adb tunnel anymore
|
||||||
disable_tunnel(server); // ignore failure
|
disable_tunnel(serial); // ignore failure
|
||||||
server->tunnel_enabled = SDL_FALSE;
|
server->adb_reverse_enabled = SDL_FALSE;
|
||||||
|
|
||||||
return server->device_socket;
|
return server->device_socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
void server_stop(struct server *server) {
|
void server_stop(struct server *server, const char *serial) {
|
||||||
SDL_assert(server->process != PROCESS_NONE);
|
SDL_assert(server->process != PROCESS_NONE);
|
||||||
|
|
||||||
if (!cmd_terminate(server->process)) {
|
if (!cmd_terminate(server->process)) {
|
||||||
@@ -234,13 +151,13 @@ void server_stop(struct server *server) {
|
|||||||
cmd_simple_wait(server->process, NULL); // ignore exit code
|
cmd_simple_wait(server->process, NULL); // ignore exit code
|
||||||
LOGD("Server terminated");
|
LOGD("Server terminated");
|
||||||
|
|
||||||
if (server->tunnel_enabled) {
|
if (server->adb_reverse_enabled) {
|
||||||
// ignore failure
|
// ignore failure
|
||||||
disable_tunnel(server);
|
disable_tunnel(serial);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (server->server_copied_to_device) {
|
if (server->server_copied_to_device) {
|
||||||
remove_server(server->serial); // ignore failure
|
remove_server(serial); // ignore failure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,5 +168,4 @@ void server_destroy(struct server *server) {
|
|||||||
if (server->device_socket != INVALID_SOCKET) {
|
if (server->device_socket != INVALID_SOCKET) {
|
||||||
close_socket(&server->device_socket);
|
close_socket(&server->device_socket);
|
||||||
}
|
}
|
||||||
SDL_free((void *) server->serial);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,24 +5,18 @@
|
|||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
|
||||||
struct server {
|
struct server {
|
||||||
const char *serial;
|
|
||||||
process_t process;
|
process_t process;
|
||||||
socket_t server_socket; // only used if !tunnel_forward
|
socket_t server_socket;
|
||||||
socket_t device_socket;
|
socket_t device_socket;
|
||||||
Uint16 local_port;
|
SDL_bool adb_reverse_enabled;
|
||||||
SDL_bool tunnel_enabled;
|
|
||||||
SDL_bool tunnel_forward; // use "adb forward" instead of "adb reverse"
|
|
||||||
SDL_bool server_copied_to_device;
|
SDL_bool server_copied_to_device;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SERVER_INITIALIZER { \
|
#define SERVER_INITIALIZER { \
|
||||||
.serial = NULL, \
|
|
||||||
.process = PROCESS_NONE, \
|
.process = PROCESS_NONE, \
|
||||||
.server_socket = INVALID_SOCKET, \
|
.server_socket = INVALID_SOCKET, \
|
||||||
.device_socket = INVALID_SOCKET, \
|
.device_socket = INVALID_SOCKET, \
|
||||||
.local_port = 0, \
|
.adb_reverse_enabled = SDL_FALSE, \
|
||||||
.tunnel_enabled = SDL_FALSE, \
|
|
||||||
.tunnel_forward = SDL_FALSE, \
|
|
||||||
.server_copied_to_device = SDL_FALSE, \
|
.server_copied_to_device = SDL_FALSE, \
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,10 +28,10 @@ SDL_bool server_start(struct server *server, const char *serial, Uint16 local_po
|
|||||||
Uint16 max_size, Uint32 bit_rate);
|
Uint16 max_size, Uint32 bit_rate);
|
||||||
|
|
||||||
// block until the communication with the server is established
|
// block until the communication with the server is established
|
||||||
socket_t server_connect_to(struct server *server);
|
socket_t server_connect_to(struct server *server, const char *serial, Uint32 timeout_ms);
|
||||||
|
|
||||||
// disconnect and kill the server process
|
// disconnect and kill the server process
|
||||||
void server_stop(struct server *server);
|
void server_stop(struct server *server, const char *serial);
|
||||||
|
|
||||||
// close and release sockets
|
// close and release sockets
|
||||||
void server_destroy(struct server *server);
|
void server_destroy(struct server *server);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "command.h"
|
#include "../../command.h"
|
||||||
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "net.h"
|
#include "../../net.h"
|
||||||
|
|
||||||
# include <unistd.h>
|
# include <unistd.h>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "command.h"
|
#include "../../command.h"
|
||||||
|
|
||||||
#include "log.h"
|
#include "../../log.h"
|
||||||
#include "strutil.h"
|
#include "../../strutil.h"
|
||||||
|
|
||||||
HANDLE cmd_execute(const char *path, const char *const argv[]) {
|
HANDLE cmd_execute(const char *path, const char *const argv[]) {
|
||||||
STARTUPINFO si;
|
STARTUPINFO si;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "net.h"
|
#include "../../net.h"
|
||||||
|
|
||||||
#include "log.h"
|
#include "../../log.h"
|
||||||
|
|
||||||
SDL_bool net_init(void) {
|
SDL_bool net_init(void) {
|
||||||
WSADATA wsa;
|
WSADATA wsa;
|
||||||
|
|||||||
@@ -35,36 +35,16 @@ static void test_serialize_text_event() {
|
|||||||
|
|
||||||
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
|
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
|
||||||
int size = control_event_serialize(&event, buf);
|
int size = control_event_serialize(&event, buf);
|
||||||
assert(size == 16);
|
assert(size == 15);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
0x01, // CONTROL_EVENT_TYPE_KEYCODE
|
0x01, // CONTROL_EVENT_TYPE_KEYCODE
|
||||||
0x00, 0x0d, // text length
|
0x0d, // text length
|
||||||
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_serialize_long_text_event() {
|
|
||||||
struct control_event event;
|
|
||||||
event.type = CONTROL_EVENT_TYPE_TEXT;
|
|
||||||
char text[TEXT_MAX_LENGTH];
|
|
||||||
memset(text, 'a', sizeof(text));
|
|
||||||
event.text_event.text = text;
|
|
||||||
|
|
||||||
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
|
|
||||||
int size = control_event_serialize(&event, buf);
|
|
||||||
assert(size == 3 + sizeof(text));
|
|
||||||
|
|
||||||
unsigned char expected[3 + TEXT_MAX_LENGTH];
|
|
||||||
expected[0] = 0x01; // CONTROL_EVENT_TYPE_KEYCODE
|
|
||||||
expected[1] = 0x01;
|
|
||||||
expected[2] = 0x2c; // text length (16 bits)
|
|
||||||
memset(&expected[3], 'a', TEXT_MAX_LENGTH);
|
|
||||||
|
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_serialize_mouse_event() {
|
static void test_serialize_mouse_event() {
|
||||||
struct control_event event = {
|
struct control_event event = {
|
||||||
.type = CONTROL_EVENT_TYPE_MOUSE,
|
.type = CONTROL_EVENT_TYPE_MOUSE,
|
||||||
@@ -134,7 +114,6 @@ static void test_serialize_scroll_event() {
|
|||||||
int main() {
|
int main() {
|
||||||
test_serialize_keycode_event();
|
test_serialize_keycode_event();
|
||||||
test_serialize_text_event();
|
test_serialize_text_event();
|
||||||
test_serialize_long_text_event();
|
|
||||||
test_serialize_mouse_event();
|
test_serialize_mouse_event();
|
||||||
test_serialize_scroll_event();
|
test_serialize_scroll_event();
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
@@ -3,4 +3,3 @@ option('build_server', type: 'boolean', value: true, description: 'Build the ser
|
|||||||
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
|
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
|
||||||
option('override_server_path', type: 'string', description: 'Hardcoded path to find the server at runtime')
|
option('override_server_path', type: 'string', description: 'Hardcoded path to find the server at runtime')
|
||||||
option('skip_frames', type: 'boolean', value: true, description: 'Always display the most recent frame')
|
option('skip_frames', type: 'boolean', value: true, description: 'Always display the most recent frame')
|
||||||
option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support')
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ android {
|
|||||||
compileSdkVersion 27
|
compileSdkVersion 27
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.genymobile.scrcpy"
|
applicationId "com.genymobile.scrcpy"
|
||||||
minSdkVersion 19
|
minSdkVersion 21
|
||||||
targetSdkVersion 27
|
targetSdkVersion 27
|
||||||
versionCode 2
|
versionCode 1
|
||||||
versionName "1.1"
|
versionName "1.0"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public final class ControlEvent {
|
|||||||
public static final int TYPE_SCROLL = 3;
|
public static final int TYPE_SCROLL = 3;
|
||||||
public static final int TYPE_COMMAND = 4;
|
public static final int TYPE_COMMAND = 4;
|
||||||
|
|
||||||
public static final int COMMAND_BACK_OR_SCREEN_ON = 0;
|
public static final int COMMAND_SCREEN_ON = 0;
|
||||||
|
|
||||||
private int type;
|
private int type;
|
||||||
private String text;
|
private String text;
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ public class ControlEventReader {
|
|||||||
private static final int SCROLL_PAYLOAD_LENGTH = 16;
|
private static final int SCROLL_PAYLOAD_LENGTH = 16;
|
||||||
private static final int COMMAND_PAYLOAD_LENGTH = 1;
|
private static final int COMMAND_PAYLOAD_LENGTH = 1;
|
||||||
|
|
||||||
public static final int TEXT_MAX_LENGTH = 300;
|
private static final int MAX_TEXT_LENGTH = 32;
|
||||||
private static final int RAW_BUFFER_SIZE = 1024;
|
private static final int RAW_BUFFER_SIZE = 128;
|
||||||
|
|
||||||
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
|
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
|
||||||
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
|
||||||
private final byte[] textBuffer = new byte[TEXT_MAX_LENGTH];
|
private final byte[] textBuffer = new byte[MAX_TEXT_LENGTH];
|
||||||
|
|
||||||
public ControlEventReader() {
|
public ControlEventReader() {
|
||||||
// invariant: the buffer is always in "get" mode
|
// invariant: the buffer is always in "get" mode
|
||||||
@@ -94,7 +94,7 @@ public class ControlEventReader {
|
|||||||
if (buffer.remaining() < 1) {
|
if (buffer.remaining() < 1) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int len = toUnsigned(buffer.getShort());
|
int len = toUnsigned(buffer.get());
|
||||||
if (buffer.remaining() < len) {
|
if (buffer.remaining() < len) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
import android.net.LocalServerSocket;
|
|
||||||
import android.net.LocalSocket;
|
import android.net.LocalSocket;
|
||||||
import android.net.LocalSocketAddress;
|
import android.net.LocalSocketAddress;
|
||||||
|
|
||||||
@@ -34,24 +33,8 @@ public final class DesktopConnection implements Closeable {
|
|||||||
return localSocket;
|
return localSocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LocalSocket listenAndAccept(String abstractName) throws IOException {
|
public static DesktopConnection open(Device device) throws IOException {
|
||||||
LocalServerSocket localServerSocket = new LocalServerSocket(abstractName);
|
LocalSocket socket = connect(SOCKET_NAME);
|
||||||
try {
|
|
||||||
return localServerSocket.accept();
|
|
||||||
} finally {
|
|
||||||
localServerSocket.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException {
|
|
||||||
LocalSocket socket;
|
|
||||||
if (tunnelForward) {
|
|
||||||
socket = listenAndAccept(SOCKET_NAME);
|
|
||||||
// send one byte so the client may read() to detect a connection error
|
|
||||||
socket.getOutputStream().write(0);
|
|
||||||
} else {
|
|
||||||
socket = connect(SOCKET_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
DesktopConnection connection = new DesktopConnection(socket);
|
DesktopConnection connection = new DesktopConnection(socket);
|
||||||
Size videoSize = device.getScreenInfo().getVideoSize();
|
Size videoSize = device.getScreenInfo().getVideoSize();
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ public final class Device {
|
|||||||
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo();
|
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo();
|
||||||
boolean rotated = (displayInfo.getRotation() & 1) != 0;
|
boolean rotated = (displayInfo.getRotation() & 1) != 0;
|
||||||
Size deviceSize = displayInfo.getSize();
|
Size deviceSize = displayInfo.getSize();
|
||||||
int w = deviceSize.getWidth() & ~7; // in case it's not a multiple of 8
|
int w = deviceSize.getWidth();
|
||||||
int h = deviceSize.getHeight() & ~7;
|
int h = deviceSize.getHeight();
|
||||||
if (maxSize > 0) {
|
if (maxSize > 0) {
|
||||||
if (BuildConfig.DEBUG && maxSize % 8 != 0) {
|
if (BuildConfig.DEBUG && maxSize % 8 != 0) {
|
||||||
throw new AssertionError("Max size must be a multiple of 8");
|
throw new AssertionError("Max size must be a multiple of 8");
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ public class EventController {
|
|||||||
coords.orientation = 0;
|
coords.orientation = 0;
|
||||||
coords.pressure = 1;
|
coords.pressure = 1;
|
||||||
coords.size = 1;
|
coords.size = 1;
|
||||||
|
coords.toolMajor = 1;
|
||||||
|
coords.toolMinor = 1;
|
||||||
|
coords.touchMajor = 1;
|
||||||
|
coords.touchMinor = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPointerCoords(Point point) {
|
private void setPointerCoords(Point point) {
|
||||||
@@ -89,12 +93,14 @@ public class EventController {
|
|||||||
return injectKeyEvent(action, keycode, 0, metaState);
|
return injectKeyEvent(action, keycode, 0, metaState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectChar(char c) {
|
private boolean injectText(String text) {
|
||||||
String decomposed = KeyComposition.decompose(c);
|
return injectText(text, true);
|
||||||
char[] chars = decomposed != null ? decomposed.toCharArray() : new char[] {c};
|
}
|
||||||
KeyEvent[] events = charMap.getEvents(chars);
|
|
||||||
|
private boolean injectText(String text, boolean decomposeOnFailure) {
|
||||||
|
KeyEvent[] events = charMap.getEvents(text.toCharArray());
|
||||||
if (events == null) {
|
if (events == null) {
|
||||||
return false;
|
return decomposeOnFailure ? injectDecomposition(text) : false;
|
||||||
}
|
}
|
||||||
for (KeyEvent event : events) {
|
for (KeyEvent event : events) {
|
||||||
if (!injectEvent(event)) {
|
if (!injectEvent(event)) {
|
||||||
@@ -104,9 +110,10 @@ public class EventController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectText(String text) {
|
private boolean injectDecomposition(String text) {
|
||||||
for (char c : text.toCharArray()) {
|
for (char c : text.toCharArray()) {
|
||||||
if (!injectChar(c)) {
|
String composedText = KeyComposition.decompose(c);
|
||||||
|
if (composedText == null || !injectText(composedText, false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,15 +170,10 @@ public class EventController {
|
|||||||
return device.isScreenOn() || injectKeycode(KeyEvent.KEYCODE_POWER);
|
return device.isScreenOn() || injectKeycode(KeyEvent.KEYCODE_POWER);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean pressBackOrTurnScreenOn() {
|
|
||||||
int keycode = device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER;
|
|
||||||
return injectKeycode(keycode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean executeCommand(int action) {
|
private boolean executeCommand(int action) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case ControlEvent.COMMAND_BACK_OR_SCREEN_ON:
|
case ControlEvent.COMMAND_SCREEN_ON:
|
||||||
return pressBackOrTurnScreenOn();
|
return turnScreenOn();
|
||||||
default:
|
default:
|
||||||
Ln.w("Unsupported command: " + action);
|
Ln.w("Unsupported command: " + action);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import java.util.Map;
|
|||||||
* This is useful for injecting key events to generate the expected character ({@link android.view.KeyCharacterMap#getEvents(char[])}
|
* This is useful for injecting key events to generate the expected character ({@link android.view.KeyCharacterMap#getEvents(char[])}
|
||||||
* KeyCharacterMap.getEvents()} returns {@code null} with input {@code "é"} but works with input {@code "\u0301e"}).
|
* KeyCharacterMap.getEvents()} returns {@code null} with input {@code "é"} but works with input {@code "\u0301e"}).
|
||||||
* <p>
|
* <p>
|
||||||
* See <a href="https://source.android.com/devices/input/key-character-map-files#behaviors">diacritical dead key characters</a>.
|
* See <a href="https://source.android.com/devices/input/key-character-map-files#key-declarations">diacritical dead key characters</a>.
|
||||||
*/
|
*/
|
||||||
public final class KeyComposition {
|
public final class KeyComposition {
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.genymobile.scrcpy;
|
|||||||
public class Options {
|
public class Options {
|
||||||
private int maxSize;
|
private int maxSize;
|
||||||
private int bitRate;
|
private int bitRate;
|
||||||
private boolean tunnelForward;
|
|
||||||
|
|
||||||
public int getMaxSize() {
|
public int getMaxSize() {
|
||||||
return maxSize;
|
return maxSize;
|
||||||
@@ -20,12 +19,4 @@ public class Options {
|
|||||||
public void setBitRate(int bitRate) {
|
public void setBitRate(int bitRate) {
|
||||||
this.bitRate = bitRate;
|
this.bitRate = bitRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTunnelForward() {
|
|
||||||
return tunnelForward;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTunnelForward(boolean tunnelForward) {
|
|
||||||
this.tunnelForward = tunnelForward;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.graphics.Rect;
|
|||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaCodecInfo;
|
import android.media.MediaCodecInfo;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
|
||||||
@@ -78,17 +77,11 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation") // Android API 19 requires to call deprecated methods
|
|
||||||
private boolean encode(MediaCodec codec, OutputStream outputStream) throws IOException {
|
private boolean encode(MediaCodec codec, OutputStream outputStream) throws IOException {
|
||||||
@SuppressWarnings("checkstyle:MagicNumber")
|
@SuppressWarnings("checkstyle:MagicNumber")
|
||||||
byte[] buf = new byte[bitRate / 8]; // may contain up to 1 second of video
|
byte[] buf = new byte[bitRate / 8]; // may contain up to 1 second of video
|
||||||
boolean eof = false;
|
boolean eof = false;
|
||||||
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||||
|
|
||||||
ByteBuffer[] outputBuffers = null;
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
outputBuffers = codec.getOutputBuffers();
|
|
||||||
}
|
|
||||||
while (!consumeRotationChange() && !eof) {
|
while (!consumeRotationChange() && !eof) {
|
||||||
int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
|
int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
|
||||||
eof = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
|
eof = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
|
||||||
@@ -98,12 +91,7 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (outputBufferId >= 0) {
|
if (outputBufferId >= 0) {
|
||||||
ByteBuffer outputBuffer;
|
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
outputBuffer = codec.getOutputBuffer(outputBufferId);
|
|
||||||
} else {
|
|
||||||
outputBuffer = outputBuffers[outputBufferId];
|
|
||||||
}
|
|
||||||
while (outputBuffer.hasRemaining()) {
|
while (outputBuffer.hasRemaining()) {
|
||||||
int remaining = outputBuffer.remaining();
|
int remaining = outputBuffer.remaining();
|
||||||
int len = Math.min(buf.length, remaining);
|
int len = Math.min(buf.length, remaining);
|
||||||
@@ -112,8 +100,6 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
outputBuffer.get(buf, 0, len);
|
outputBuffer.get(buf, 0, len);
|
||||||
outputStream.write(buf, 0, len);
|
outputStream.write(buf, 0, len);
|
||||||
}
|
}
|
||||||
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
|
|
||||||
outputBuffers = codec.getOutputBuffers();
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (outputBufferId >= 0) {
|
if (outputBufferId >= 0) {
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ public final class Server {
|
|||||||
|
|
||||||
private static void scrcpy(Options options) throws IOException {
|
private static void scrcpy(Options options) throws IOException {
|
||||||
final Device device = new Device(options);
|
final Device device = new Device(options);
|
||||||
boolean tunnelForward = options.isTunnelForward();
|
try (DesktopConnection connection = DesktopConnection.open(device)) {
|
||||||
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
|
|
||||||
ScreenEncoder screenEncoder = new ScreenEncoder(options.getBitRate());
|
ScreenEncoder screenEncoder = new ScreenEncoder(options.getBitRate());
|
||||||
|
|
||||||
// asynchronous
|
// asynchronous
|
||||||
@@ -56,13 +55,6 @@ public final class Server {
|
|||||||
int bitRate = Integer.parseInt(args[1]);
|
int bitRate = Integer.parseInt(args[1]);
|
||||||
options.setBitRate(bitRate);
|
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);
|
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,14 @@ package com.genymobile.scrcpy;
|
|||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class ControlEventReaderTest {
|
public class ControlEventReaderTest {
|
||||||
|
|
||||||
@@ -44,8 +43,8 @@ public class ControlEventReaderTest {
|
|||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlEvent.TYPE_TEXT);
|
dos.writeByte(ControlEvent.TYPE_TEXT);
|
||||||
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
|
||||||
dos.writeShort(text.length);
|
dos.writeByte(text.length);
|
||||||
dos.write(text);
|
dos.write("testé".getBytes(StandardCharsets.UTF_8));
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
reader.readFrom(new ByteArrayInputStream(packet));
|
||||||
@@ -55,26 +54,6 @@ public class ControlEventReaderTest {
|
|||||||
Assert.assertEquals("testé", event.getText());
|
Assert.assertEquals("testé", event.getText());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseLongTextEvent() throws IOException {
|
|
||||||
ControlEventReader reader = new ControlEventReader();
|
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
|
||||||
dos.writeByte(ControlEvent.TYPE_TEXT);
|
|
||||||
byte[] text = new byte[ControlEventReader.TEXT_MAX_LENGTH];
|
|
||||||
Arrays.fill(text, (byte) 'a');
|
|
||||||
dos.writeShort(text.length);
|
|
||||||
dos.write(text);
|
|
||||||
byte[] packet = bos.toByteArray();
|
|
||||||
|
|
||||||
reader.readFrom(new ByteArrayInputStream(packet));
|
|
||||||
ControlEvent event = reader.next();
|
|
||||||
|
|
||||||
Assert.assertEquals(ControlEvent.TYPE_TEXT, event.getType());
|
|
||||||
Assert.assertEquals(new String(text, StandardCharsets.US_ASCII), event.getText());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParseMouseEvent() throws IOException {
|
public void testParseMouseEvent() throws IOException {
|
||||||
ControlEventReader reader = new ControlEventReader();
|
ControlEventReader reader = new ControlEventReader();
|
||||||
|
|||||||
Reference in New Issue
Block a user