Compare commits

..

15 Commits

Author SHA1 Message Date
f595d16cde wip 2021-10-21 10:17:03 +02:00
74fb32a584 Retrieve device serial for AOA
The serial is necessary to find the correct Android device for AOA.

If it is not explicitly provided by the user via -s, then execute "adb
getserialno" to retrieve it.
2021-10-21 09:59:24 +02:00
2f29b2e25d Expose function to get the device serial
Expose adb_get_serialno() to retrieve the device serial via the command
"adb getserialno".
2021-10-21 09:59:24 +02:00
710f7e1a7c Add read_pipe_all()
Add a convenience function to read from a pipe until all requested data
has been read.
2021-10-21 09:59:24 +02:00
f80c5107db Expose adb execution with redirection
Expose the redirection feature to the adb API.
2021-10-21 09:59:24 +02:00
0ed00c2e40 Add command execution with redirection
Expose command execution with pipes to stdin, stdout and stderr.

This will allow to read the result of adb commands.
2021-10-21 09:59:24 +02:00
874a4967a4 Support USB HID over AoAv2 mode for keyboard input
This provides a better input experience via
simulate physical keyboard, it converts
SDL_KeyboardEvent to proper HID events and send it
via HID over AoAv2.

This is a rewriting and bugfix of the origin code
from [@amosbird](https://github.com/amosbird).

Make sdl_keymod_to_hid_modifiers() more readable

Support MOD keys in HID mode

Enable Ctrl+V on HID mode

Support to send media events from hid_keyboard

Use existing --serial to replace --usb

Use explict option for input mode

Move HID keyboard setup code into functions

Send HID events in separated thread

Let libusb handle max package size

Fix HID keyboard report desc
2021-10-21 09:58:48 +02:00
a52779ae6b Extract mouse processor trait
Mainly for consistency with the keyboard processor trait.

This could allow to provide alternative mouse processors.
2021-10-21 09:58:48 +02:00
c8f65647f2 Extract keyboard processor trait
This will allow to provide alternative key processors.
2021-10-21 09:58:48 +02:00
b89c23b7f5 Fix trait header guards 2021-10-21 09:58:48 +02:00
7f3d2eded7 Fix "Could not found v4l2 muxer"
The AVOutputFormat name is a comma-separated list. In theory, possible
names for V4L2 are:

    - "video4linux2,v4l2"
    - "v4l2,video4linux2"
    - "v4l2"
    - "video4linux2"

To find the muxer in all cases, we must request exactly one muxer name
at a time.

PR #2718 <https://github.com/Genymobile/scrcpy/pull/2718>

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-10-21 09:58:48 +02:00
1d4e15f0d5 Simplify net_send_all()
There is no need to declare the variable before the loop.
2021-10-21 09:58:48 +02:00
ee55118282 Fix workarounds for Meizu
Workarounds.fillAppInfo() is necessary for Meizu devices even before the
first call to internalStreamScreen(), but it is harmful on other
devices (#940).

Therefore, simplify the workaround, by calling fillAppInfo() only if
Build.BRAND equals "meizu" (case insensitive).

Fixes #240 <https://github.com/Genymobile/scrcpy/issues/240> (again)
Fixes #2656 <https://github.com/Genymobile/scrcpy/issues/2656>
2021-10-21 09:58:48 +02:00
4c6096388e Add missing includes
Refs #2616 <https://github.com/Genymobile/scrcpy/issues/2616>
2021-10-21 09:58:48 +02:00
691bdb925f Add support for expandNotificationsPanel() variant
Some custom vendor ROM added an int as a parameter.

Fixes #2551 <https://github.com/Genymobile/scrcpy/issues/2551>
2021-10-21 09:58:48 +02:00
103 changed files with 2832 additions and 5333 deletions

View File

@ -14,8 +14,7 @@ First, you need to install the required packages:
# for Debian/Ubuntu # for Debian/Ubuntu
sudo apt install ffmpeg libsdl2-2.0-0 adb wget \ sudo apt install ffmpeg libsdl2-2.0-0 adb wget \
gcc git pkg-config meson ninja-build libsdl2-dev \ gcc git pkg-config meson ninja-build libsdl2-dev \
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \ libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev
libusb-1.0-0 libusb-dev
``` ```
Then clone the repo and execute the installation script Then clone the repo and execute the installation script
@ -161,7 +160,8 @@ install the required packages:
```bash ```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 \
mingw-w64-x86_64-libusb
# client build dependencies # client build dependencies
pacman -S mingw-w64-x86_64-make \ pacman -S mingw-w64-x86_64-make \
@ -175,7 +175,8 @@ For a 32 bits version, replace `x86_64` by `i686`:
```bash ```bash
# runtime dependencies # runtime dependencies
pacman -S mingw-w64-i686-SDL2 \ pacman -S mingw-w64-i686-SDL2 \
mingw-w64-i686-ffmpeg mingw-w64-i686-ffmpeg \
mingw-w64-i686-libusb
# client build dependencies # client build dependencies
pacman -S mingw-w64-i686-make \ pacman -S mingw-w64-i686-make \
@ -199,7 +200,7 @@ Install the packages with [Homebrew]:
```bash ```bash
# runtime dependencies # runtime dependencies
brew install sdl2 ffmpeg brew install sdl2 ffmpeg libusb
# client build dependencies # client build dependencies
brew install pkg-config meson brew install pkg-config meson
@ -270,10 +271,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server #### Option 2: Use prebuilt server
- [`scrcpy-server-v1.20`][direct-scrcpy-server] - [`scrcpy-server-v1.19`][direct-scrcpy-server]
_(SHA-256: b20aee4951f99b060c4a44000ba94de973f9604758ef62beb253b371aad3df34)_ _(SHA-256: 876f9322182e6aac6a58db1334f4225855ef3a17eaebc80aab6601d9d1ecb867)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.20/scrcpy-server-v1.20 [direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.19/scrcpy-server-v1.19
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:

31
FAQ.md
View File

@ -118,17 +118,13 @@ In developer options, enable:
### Special characters do not work ### Special characters do not work
The default text injection method is [limited to ASCII characters][text-input]. Injecting text input is [limited to ASCII characters][text-input]. A trick
A trick allows to also inject some [accented characters][accented-characters], allows to also inject some [accented characters][accented-characters], but
but that's all. See [#37]. that's all. See [#37].
Since scrcpy v1.20 on Linux, it is possible to simulate a [physical
keyboard][hid] (HID).
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode [text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters [accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
[#37]: https://github.com/Genymobile/scrcpy/issues/37 [#37]: https://github.com/Genymobile/scrcpy/issues/37
[hid]: README.md#physical-keyboard-simulation-hid
## Client issues ## Client issues
@ -222,27 +218,6 @@ scrcpy -m 800
You could also try another [encoder](README.md#encoder). You could also try another [encoder](README.md#encoder).
If you encounter this exception on Android 12, then just upgrade to scrcpy >=
1.18 (see [#2129]):
```
> ERROR: Exception on thread Thread[main,5,main]
java.lang.AssertionError: java.lang.reflect.InvocationTargetException
at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:75)
...
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:73)
... 7 more
Caused by: java.lang.IllegalArgumentException: displayToken must not be null
at android.view.SurfaceControl$Transaction.setDisplaySurface(SurfaceControl.java:3067)
at android.view.SurfaceControl.setDisplaySurface(SurfaceControl.java:2147)
... 9 more
```
[#2129]: https://github.com/Genymobile/scrcpy/issues/2129
## Command line on Windows ## Command line on Windows
Some Windows users are not familiar with the command line. Here is how to open a Some Windows users are not familiar with the command line. Here is how to open a

View File

@ -1,6 +1,6 @@
_Only the original [README](README.md) is guaranteed to be up-to-date._ _Only the original [README](README.md) is guaranteed to be up-to-date._
# scrcpy (v1.19) # scrcpy (v1.17)
このアプリケーションはUSB(もしくは[TCP/IP経由][article-tcpip])で接続されたAndroidデバイスの表示と制御を提供します。このアプリケーションは _root_ でのアクセスを必要としません。このアプリケーションは _GNU/Linux__Windows_ そして _macOS_ 上で動作します。 このアプリケーションはUSB(もしくは[TCP/IP経由][article-tcpip])で接続されたAndroidデバイスの表示と制御を提供します。このアプリケーションは _root_ でのアクセスを必要としません。このアプリケーションは _GNU/Linux__Windows_ そして _macOS_ 上で動作します。
@ -103,22 +103,19 @@ scoop install adb # まだ入手していない場合
brew install scrcpy brew install scrcpy
``` ```
`PATH`からアクセス可能な`adb`が必要です。もし持っていない場合はインストールしてください。 `PATH`から`adb`へのアクセスが必要です。もしまだ持っていない場合:
```bash ```bash
brew install android-platform-tools # Homebrew >= 2.6.0
brew install --cask android-platform-tools
# Homebrew < 2.6.0
brew cask install android-platform-tools
``` ```
`adb`は[MacPorts]からでもインストールできます。
```bash
sudo port install scrcpy
```
[MacPorts]: https://www.macports.org/
また、[アプリケーションをビルド][BUILD]することも可能です。 また、[アプリケーションをビルド][BUILD]することも可能です。
## 実行 ## 実行
Androidデバイスを接続し、実行: Androidデバイスを接続し、実行:
@ -187,11 +184,10 @@ scrcpy --crop 1224:1440:0:0 # オフセット位置(0,0)で1224x1440
ミラーリングの向きをロックするには: ミラーリングの向きをロックするには:
```bash ```bash
scrcpy --lock-video-orientation # 現在の向き scrcpy --lock-video-orientation 0 # 自然な向き
scrcpy --lock-video-orientation=0 # 自然な向き scrcpy --lock-video-orientation 1 # 90°反時計回り
scrcpy --lock-video-orientation=1 # 90°反時計回り scrcpy --lock-video-orientation 2 # 180°
scrcpy --lock-video-orientation=2 # 180° scrcpy --lock-video-orientation 3 # 90°時計回り
scrcpy --lock-video-orientation=3 # 90°時計回り
``` ```
この設定は録画の向きに影響します。 この設定は録画の向きに影響します。
@ -214,9 +210,7 @@ scrcpy --encoder OMX.qcom.video.encoder.avc
scrcpy --encoder _ scrcpy --encoder _
``` ```
### キャプチャ ### 録画
#### 録画
ミラーリング中に画面の録画をすることが可能です: ミラーリング中に画面の録画をすることが可能です:
@ -239,77 +233,6 @@ scrcpy -Nr file.mkv
[パケット遅延のバリエーション]: https://en.wikipedia.org/wiki/Packet_delay_variation [パケット遅延のバリエーション]: https://en.wikipedia.org/wiki/Packet_delay_variation
#### v4l2loopback
Linuxでは、ビデオストリームをv4l2ループバックデバイスに送信することができます。
v4l2loopbackのデバイスにビデオストリームを送信することで、Androidデバイスをウェブカメラのようにv4l2対応ツールで開くこともできます。
`v4l2loopback` モジュールのインストールが必要です。
```bash
sudo apt install v4l2loopback-dkms
```
v4l2デバイスを作成する。
```bash
sudo modprobe v4l2loopback
```
これにより、新しいビデオデバイスが `/dev/videoN` に作成されます。(`N` は整数)
(複数のデバイスや特定のIDのデバイスを作成するために、より多くの[オプション](https://github.com/umlaeute/v4l2loopback#options)が利用可能です。
多くの[オプション]()が利用可能で複数のデバイスや特定のIDのデバイスを作成できます。
有効なデバイスを一覧表示する:
```bash
# v4l-utilsパッケージが必要
v4l2-ctl --list-devices
# シンプルですが十分これで確認できます
ls /dev/video*
```
v4l2シンクを使用してscrcpyを起動する。
```bash
scrcpy --v4l2-sink=/dev/videoN
scrcpy --v4l2-sink=/dev/videoN --no-display # ミラーリングウィンドウを無効化する
scrcpy --v4l2-sink=/dev/videoN -N # 短縮版
```
(`N` をデバイス ID に置き換えて、`ls /dev/video*` で確認してください)
有効にすると、v4l2対応のツールでビデオストリームを開けます。
```bash
ffplay -i /dev/videoN
vlc v4l2:///dev/videoN # VLCではバッファリングの遅延が発生する場合があります
```
例えばですが [OBS]の中にこの映像を取り込めことができます。
[OBS]: https://obsproject.com/
#### Buffering
バッファリングを追加することも可能です。これによりレイテンシーは増加しますが、ジッターは減少します。(参照
[#2464])
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
このオプションでディスプレイバッファリングを設定できます。
```bash
scrcpy --display-buffer=50 # ディスプレイに50msのバッファリングを追加する
```
V4L2の場合はこちらのオプションで設定できます。
```bash
scrcpy --v4l2-buffer=500 # add 500 ms buffering for v4l2 sink
```
### 接続 ### 接続
@ -534,6 +457,16 @@ scrcpy -Sw
``` ```
#### 期限切れフレームをレンダリングする
初期状態では、待ち時間を最小限にするために、_scrcpy_ は最後にデコードされたフレームをレンダリングし、前のフレームを削除します。
全フレームのレンダリングを強制するには(待ち時間が長くなる可能性があります):
```bash
scrcpy --render-expired-frames
```
#### タッチを表示 #### タッチを表示
プレゼンテーションの場合(物理デバイス上で)物理的なタッチを表示すると便利な場合があります。 プレゼンテーションの場合(物理デバイス上で)物理的なタッチを表示すると便利な場合があります。
@ -653,14 +586,14 @@ APKをインストールするには、(`.apk`で終わる)APKファイルを _s
#### デバイスにファイルを送る #### デバイスにファイルを送る
デバイスの`/sdcard/Download`ディレクトリにファイルを送るには、(APKではない)ファイルを _scrcpy_ の画面にドラッグ&ドロップします。 デバイスの`/sdcard/`ディレクトリにファイルを送るには、(APKではない)ファイルを _scrcpy_ の画面にドラッグ&ドロップします。
見た目のフィードバックはありません。コンソールにログが出力されます。 見た目のフィードバックはありません。コンソールにログが出力されます。
転送先ディレクトリを起動時に変更することができます: 転送先ディレクトリを起動時に変更することができます:
```bash ```bash
scrcpy --push-target=/sdcard/Movies/ scrcpy --push-target /sdcard/foo/bar/
``` ```
@ -701,7 +634,7 @@ _<kbd>[Super]</kbd>は通常<kbd>Windows</kbd>もしくは<kbd>Cmd</kbd>キー
| ウィンドウサイズを変更して黒い境界線を削除 | <kbd>MOD</kbd>+<kbd>w</kbd> \| _ダブルクリック¹_ | ウィンドウサイズを変更して黒い境界線を削除 | <kbd>MOD</kbd>+<kbd>w</kbd> \| _ダブルクリック¹_
| `HOME`をクリック | <kbd>MOD</kbd>+<kbd>h</kbd> \| _真ん中クリック_ | `HOME`をクリック | <kbd>MOD</kbd>+<kbd>h</kbd> \| _真ん中クリック_
| `BACK`をクリック | <kbd>MOD</kbd>+<kbd>b</kbd> \| _右クリック²_ | `BACK`をクリック | <kbd>MOD</kbd>+<kbd>b</kbd> \| _右クリック²_
| `APP_SWITCH`をクリック | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4クリック³_ | `APP_SWITCH`をクリック | <kbd>MOD</kbd>+<kbd>s</kbd>
| `MENU` (画面のアンロック)をクリック | <kbd>MOD</kbd>+<kbd>m</kbd> | `MENU` (画面のアンロック)をクリック | <kbd>MOD</kbd>+<kbd>m</kbd>
| `VOLUME_UP`をクリック | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上)_ | `VOLUME_UP`をクリック | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上)_
| `VOLUME_DOWN`をクリック | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下)_ | `VOLUME_DOWN`をクリック | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下)_
@ -710,8 +643,7 @@ _<kbd>[Super]</kbd>は通常<kbd>Windows</kbd>もしくは<kbd>Cmd</kbd>キー
| デバイス画面をオフにする(ミラーリングしたまま) | <kbd>MOD</kbd>+<kbd>o</kbd> | デバイス画面をオフにする(ミラーリングしたまま) | <kbd>MOD</kbd>+<kbd>o</kbd>
| デバイス画面をオンにする | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd> | デバイス画面をオンにする | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| デバイス画面を回転する | <kbd>MOD</kbd>+<kbd>r</kbd> | デバイス画面を回転する | <kbd>MOD</kbd>+<kbd>r</kbd>
| 通知パネルを展開する | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5ボタンクリック³_ | 通知パネルを展開する | <kbd>MOD</kbd>+<kbd>n</kbd>
| 設定パネルを展開する | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _5ダブルクリック³_
| 通知パネルを折りたたむ | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd> | 通知パネルを折りたたむ | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| クリップボードへのコピー³ | <kbd>MOD</kbd>+<kbd>c</kbd> | クリップボードへのコピー³ | <kbd>MOD</kbd>+<kbd>c</kbd>
| クリップボードへのカット³ | <kbd>MOD</kbd>+<kbd>x</kbd> | クリップボードへのカット³ | <kbd>MOD</kbd>+<kbd>x</kbd>
@ -722,17 +654,11 @@ _<kbd>[Super]</kbd>は通常<kbd>Windows</kbd>もしくは<kbd>Cmd</kbd>キー
_¹黒い境界線を削除するため、境界線上でダブルクリック_ _¹黒い境界線を削除するため、境界線上でダブルクリック_
_²もしスクリーンがオフの場合、右クリックでスクリーンをオンする。それ以外の場合はBackを押します._ _²もしスクリーンがオフの場合、右クリックでスクリーンをオンする。それ以外の場合はBackを押します._
4と5はマウスのボタンです、もしあなたのマウスにボタンがあれば使えます._ Android 7以上のみ._
_⁴Android 7以上のみ._
キーを繰り返すショートカットはキーを離して2回目を押したら実行されます。例えば「設定パネルを展開する」を実行する場合は以下のように操作する。
1. <kbd>MOD</kbd> キーを押し、押したままにする.
2. その後に <kbd>n</kbd>キーを2回押す.
3. 最後に <kbd>MOD</kbd>キーを離す.
全ての<kbd>Ctrl</kbd>+_キー_ ショートカットはデバイスに転送されます、そのためアクティブなアプリケーションによって処理されます。 全ての<kbd>Ctrl</kbd>+_キー_ ショートカットはデバイスに転送されます、そのためアクティブなアプリケーションによって処理されます。
## カスタムパス ## カスタムパス
特定の _adb_ バイナリを使用する場合、そのパスを環境変数`ADB`で構成します: 特定の _adb_ バイナリを使用する場合、そのパスを環境変数`ADB`で構成します:

139
README.md
View File

@ -1,37 +1,24 @@
# scrcpy (v1.20) # scrcpy (v1.19)
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
[Read in another language](#translations) [Read in another language](#translations)
This application provides display and control of Android devices connected via This application provides display and control of Android devices connected on
USB (or [over TCP/IP](#wireless)). It does not require any _root_ access. USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
It works on _GNU/Linux_, _Windows_ and _macOS_. It works on _GNU/Linux_, _Windows_ and _macOS_.
![screenshot](assets/screenshot-debian-600.jpg) ![screenshot](assets/screenshot-debian-600.jpg)
It focuses on: It focuses on:
- **lightness**: native, displays only the device screen - **lightness** (native, displays only the device screen)
- **performance**: 30~120fps, depending on the device - **performance** (30~60fps)
- **quality**: 1920×1080 or above - **quality** (1920×1080 or above)
- **low latency**: [35~70ms][lowlatency] - **low latency** ([35~70ms][lowlatency])
- **low startup time**: ~1 second to display the first image - **low startup time** (~1 second to display the first image)
- **non-intrusiveness**: nothing is left installed on the device - **non-intrusiveness** (nothing is left installed on the device)
- **user benefits**: no account, no ads, no internet required
- **freedom**: free and open source software
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646 [lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
Its features include:
- [recording](#recording)
- mirroring with [device screen off](#turn-screen-off)
- [copy-paste](#copy-paste) in both directions
- [configurable quality](#capture-configuration)
- device screen [as a webcam (V4L2)](#v4l2loopback) (Linux-only)
- [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid)
(Linux-only)
- and more…
## Requirements ## Requirements
@ -101,10 +88,10 @@ process][BUILD_simple]).
For Windows, for simplicity, a prebuilt archive with all the dependencies For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available: (including `adb`) is available:
- [`scrcpy-win64-v1.20.zip`][direct-win64] - [`scrcpy-win64-v1.19.zip`][direct-win64]
_(SHA-256: 548532b616288bcaeceff6881ad5e6f0928e5ae2b48c380385f03627401cfdba)_ _(SHA-256: 383d6483f25ac0092d4bb9fef6c967351ecd50fc248e0c82932db97d6d32f11b)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.20/scrcpy-win64-v1.20.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.19/scrcpy-win64-v1.19.zip
It is also available in [Chocolatey]: It is also available in [Chocolatey]:
@ -220,6 +207,29 @@ scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
If `--max-size` is also specified, resizing is applied after cropping. If `--max-size` is also specified, resizing is applied after cropping.
#### USB HID over AOAv2
Scrcpy can simulate a USB physical keyboard on Android to provide better input
experience, you need to connect your device via USB, not wireless.
However, due to some limitation of libusb and WinUSB driver, you cannot use HID
over AOAv2 on Windows.
Currently a USB serial number is needed to use HID over AOAv2.
```bash
scrcpy --serial XXXXXXXXXXXXXXXX # don't use HID
scrcpy --serial XXXXXXXXXXXXXXXX --input-mode inject # don't use HID
scrcpy --serial XXXXXXXXXXXXXXXX --input-mode hid # try HID and exit if failed
```
Serial number can be found by `adb get-serialno`.
If you are a non-QWERTY keyboard user and using HID mode, please remember to set
correct physical keyboard layout manually in Android settings, because scrcpy
just forwards scancodes to Android device and Android system is responsible for
converting scancodes to correct keycode on Android device (your system does this
on your PC).
#### Lock video orientation #### Lock video orientation
@ -416,27 +426,17 @@ autoadb scrcpy -s '{}'
To connect to a remote device, it is possible to connect a local `adb` client to To connect to a remote device, it is possible to connect a local `adb` client to
a remote `adb` server (provided they use the same version of the _adb_ a remote `adb` server (provided they use the same version of the _adb_
protocol). protocol):
First, make sure the ADB server is running on the remote computer:
```bash ```bash
adb start-server adb kill-server # kill the local adb server on 5037
``` ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
Then, establish a SSH tunnel:
```bash
# local 5038 --> remote 5037
# local 27183 <-- remote 27183
ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer
# keep this open # keep this open
``` ```
From another terminal, run scrcpy: From another terminal:
```bash ```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy scrcpy
``` ```
@ -444,16 +444,14 @@ To avoid enabling remote port forwarding, you could force a forward connection
instead (notice the `-L` instead of `-R`): instead (notice the `-L` instead of `-R`):
```bash ```bash
# local 5038 --> remote 5037 adb kill-server # kill the local adb server on 5037
# local 27183 --> remote 27183 ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
# keep this open # keep this open
``` ```
From another terminal, run scrcpy: From another terminal:
```bash ```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy --force-adb-forward scrcpy --force-adb-forward
``` ```
@ -607,14 +605,6 @@ scrcpy --turn-screen-off --stay-awake
scrcpy -Sw scrcpy -Sw
``` ```
#### Power off on close
To turn the device screen off when closing scrcpy:
```bash
scrcpy --power-off-on-close
```
#### Show touches #### Show touches
@ -706,39 +696,6 @@ content (if supported by the app) relative to the center of the screen.
Concretely, scrcpy generates additional touch events from a "virtual finger" at Concretely, scrcpy generates additional touch events from a "virtual finger" at
a location inverted through the center of the screen. a location inverted through the center of the screen.
#### Physical keyboard simulation (HID)
By default, scrcpy uses Android key or text injection: it works everywhere, but
is limited to ASCII.
On Linux, scrcpy can simulate a physical USB keyboard on Android to provide a
better input experience (using [USB HID over AOAv2][hid-aoav2]): the virtual
keyboard is disabled and it works for all characters and IME.
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
However, it only works if the device is connected by USB, and is currently only
supported on Linux.
To enable this mode:
```bash
scrcpy --hid-keyboard
scrcpy -K # short version
```
If it fails for some reason (for example because the device is not connected via
USB), it automatically fallbacks to the default mode (with a log in the
console). This allows to use the same command line options when connected over
USB and TCP/IP.
In this mode, raw key events (scancodes) are sent to the device, independently
of the host key mapping. Therefore, if your keyboard layout does not match, it
must be configured on the Android device, in Settings → System → Languages and
input → [Physical keyboard].
[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
#### Text injection preference #### Text injection preference
@ -758,9 +715,6 @@ scrcpy --prefer-text
(but this will break keyboard behavior in games) (but this will break keyboard behavior in games)
This option has no effect on HID keyboard (all key events are sent as
scancodes in this mode).
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input [textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343 [prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
@ -776,9 +730,6 @@ To avoid forwarding repeated key events:
scrcpy --no-key-repeat scrcpy --no-key-repeat
``` ```
This option has no effect on HID keyboard (key repeat is handled by Android
directly in this mode).
#### Right-click and middle-click #### Right-click and middle-click
@ -871,8 +822,6 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> | Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd> | Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_ | Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
| Drag & drop APK file | Install APK from computer
| Drag & drop non-APK file | [Push file to device](#push-file-to-device)
_¹Double-click on black borders to remove them._ _¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._ _²Right-click turns the screen on if it was off, presses BACK otherwise._
@ -902,7 +851,7 @@ ADB=/path/to/adb scrcpy
To override the path of the `scrcpy-server` file, configure its path in To override the path of the `scrcpy-server` file, configure its path in
`SCRCPY_SERVER_PATH`. `SCRCPY_SERVER_PATH`.
To override the icon, configure its path in `SCRCPY_ICON_PATH`. [useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
## Why _scrcpy_? ## Why _scrcpy_?
@ -963,7 +912,7 @@ This README is available in other languages:
- [Indonesian (Indonesia, `id`) - v1.16](README.id.md) - [Indonesian (Indonesia, `id`) - v1.16](README.id.md)
- [Italiano (Italiano, `it`) - v1.19](README.it.md) - [Italiano (Italiano, `it`) - v1.19](README.it.md)
- [日本語 (Japanese, `jp`) - v1.19](README.jp.md) - [日本語 (Japanese, `jp`) - v1.17](README.jp.md)
- [한국어 (Korean, `ko`) - v1.11](README.ko.md) - [한국어 (Korean, `ko`) - v1.11](README.ko.md)
- [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md) - [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md)
- [Español (Spanish, `sp`) - v1.17](README.sp.md) - [Español (Spanish, `sp`) - v1.17](README.sp.md)

View File

@ -1,7 +1,6 @@
src = [ src = [
'src/main.c', 'src/main.c',
'src/adb.c', 'src/adb.c',
'src/adb_tunnel.c',
'src/cli.c', 'src/cli.c',
'src/clock.c', 'src/clock.c',
'src/compat.c', 'src/compat.c',
@ -9,7 +8,6 @@ src = [
'src/controller.c', 'src/controller.c',
'src/decoder.c', 'src/decoder.c',
'src/device_msg.c', 'src/device_msg.c',
'src/icon.c',
'src/file_handler.c', 'src/file_handler.c',
'src/fps_counter.c', 'src/fps_counter.c',
'src/frame_buffer.c', 'src/frame_buffer.c',
@ -17,38 +15,26 @@ src = [
'src/keyboard_inject.c', 'src/keyboard_inject.c',
'src/mouse_inject.c', 'src/mouse_inject.c',
'src/opengl.c', 'src/opengl.c',
'src/options.c',
'src/receiver.c', 'src/receiver.c',
'src/recorder.c', 'src/recorder.c',
'src/scrcpy.c', 'src/scrcpy.c',
'src/screen.c', 'src/screen.c',
'src/server.c', 'src/server.c',
'src/stream.c', 'src/stream.c',
'src/tiny_xpm.c',
'src/video_buffer.c', 'src/video_buffer.c',
'src/util/file.c',
'src/util/intr.c',
'src/util/log.c', 'src/util/log.c',
'src/util/net.c', 'src/util/net.c',
'src/util/net_intr.c',
'src/util/process.c', 'src/util/process.c',
'src/util/process_intr.c', 'src/util/str_util.c',
'src/util/strbuf.c',
'src/util/str.c',
'src/util/term.c',
'src/util/thread.c', 'src/util/thread.c',
'src/util/tick.c', 'src/util/tick.c',
] ]
if host_machine.system() == 'windows' if host_machine.system() == 'windows'
src += [ src += [ 'src/sys/win/process.c' ]
'src/sys/win/file.c',
'src/sys/win/process.c',
]
else else
src += [ src += [ 'src/sys/unix/process.c' ]
'src/sys/unix/file.c',
'src/sys/unix/process.c',
]
endif endif
v4l2_support = host_machine.system() == 'linux' v4l2_support = host_machine.system() == 'linux'
@ -179,9 +165,6 @@ executable('scrcpy', src,
c_args: []) c_args: [])
install_man('scrcpy.1') install_man('scrcpy.1')
install_data('../data/icon.png',
rename: 'scrcpy.png',
install_dir: 'share/icons/hicolor/256x256/apps')
### TESTS ### TESTS
@ -198,10 +181,7 @@ if get_option('buildtype') == 'debug'
['test_cli', [ ['test_cli', [
'tests/test_cli.c', 'tests/test_cli.c',
'src/cli.c', 'src/cli.c',
'src/options.c', 'src/util/str_util.c',
'src/util/str.c',
'src/util/strbuf.c',
'src/util/term.c',
]], ]],
['test_clock', [ ['test_clock', [
'tests/test_clock.c', 'tests/test_clock.c',
@ -210,8 +190,7 @@ if get_option('buildtype') == 'debug'
['test_control_msg_serialize', [ ['test_control_msg_serialize', [
'tests/test_control_msg_serialize.c', 'tests/test_control_msg_serialize.c',
'src/control_msg.c', 'src/control_msg.c',
'src/util/str.c', 'src/util/str_util.c',
'src/util/strbuf.c',
]], ]],
['test_device_msg_deserialize', [ ['test_device_msg_deserialize', [
'tests/test_device_msg_deserialize.c', 'tests/test_device_msg_deserialize.c',
@ -220,14 +199,9 @@ if get_option('buildtype') == 'debug'
['test_queue', [ ['test_queue', [
'tests/test_queue.c', 'tests/test_queue.c',
]], ]],
['test_strbuf', [ ['test_strutil', [
'tests/test_strbuf.c', 'tests/test_strutil.c',
'src/util/strbuf.c', 'src/util/str_util.c',
]],
['test_str', [
'tests/test_str.c',
'src/util/str.c',
'src/util/strbuf.c',
]], ]],
] ]

View File

@ -83,13 +83,23 @@ Start in fullscreen.
Print this help. Print this help.
.TP .TP
.B \-K, \-\-hid\-keyboard .B \-K, \-\-keyboard\-hid
Simulate a physical keyboard by using HID over AOAv2. Simulate a physical keyboard by using HID over AOAv2.
This provides a better experience for IME users, and allows to generate non-ASCII characters, contrary to the default injection method. This provides a better experience for IME users, and allows to generate non-ASCII characters, contrary to the default injection method.
It may only work over USB, and is currently only supported on Linux. It may only work over USB, and is currently only supported on Linux.
.TP
.B \-i, \-\-input\-mode mode
Select input mode for keyboard events.
Possible values are "hid" and "inject".
"hid" uses Android's USB HID over AOAv2 feature to simulate physical keyboard's events, which provides better experience for IME users if supported by you device.
"inject" is the legacy scrcpy way by injecting keycode events on Android, works on most devices and is the default.
.TP .TP
.B \-\-legacy\-paste .B \-\-legacy\-paste
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v). Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).
@ -136,10 +146,6 @@ Set the TCP port (range) used by the client to listen.
Default is 27183:27199. Default is 27183:27199.
.TP
.B \-\-power\-off\-on\-close
Turn the device screen off when closing scrcpy.
.TP .TP
.B \-\-prefer\-text .B \-\-prefer\-text
Inject alpha characters and space as text events instead of key events. Inject alpha characters and space as text events instead of key events.
@ -368,10 +374,6 @@ Pinch-to-zoom from the center of the screen
.B Drag & drop APK file .B Drag & drop APK file
Install APK from computer Install APK from computer
.TP
.B Drag & drop non-APK file
Push file to device (see \fB\-\-push\-target\fR)
.SH Environment variables .SH Environment variables

View File

@ -5,10 +5,8 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "util/file.h"
#include "util/log.h" #include "util/log.h"
#include "util/process_intr.h" #include "util/str_util.h"
#include "util/str.h"
static const char *adb_command; static const char *adb_command;
@ -70,7 +68,7 @@ show_adb_installation_msg() {
{"pacman", "pacman -S android-tools"}, {"pacman", "pacman -S android-tools"},
}; };
for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) { for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) {
if (sc_file_executable_exists(pkg_managers[i].binary)) { if (search_executable(pkg_managers[i].binary)) {
LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command); LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command);
return; return;
} }
@ -82,7 +80,7 @@ show_adb_installation_msg() {
} }
static void static void
show_adb_err_msg(enum sc_process_result err, const char *const argv[]) { show_adb_err_msg(enum process_result err, const char *const argv[]) {
#define MAX_COMMAND_STRING_LEN 1024 #define MAX_COMMAND_STRING_LEN 1024
char *buf = malloc(MAX_COMMAND_STRING_LEN); char *buf = malloc(MAX_COMMAND_STRING_LEN);
if (!buf) { if (!buf) {
@ -91,18 +89,18 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) {
} }
switch (err) { switch (err) {
case SC_PROCESS_ERROR_GENERIC: case PROCESS_ERROR_GENERIC:
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN); argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
LOGE("Failed to execute: %s", buf); LOGE("Failed to execute: %s", buf);
break; break;
case SC_PROCESS_ERROR_MISSING_BINARY: case PROCESS_ERROR_MISSING_BINARY:
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN); argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
LOGE("Command not found: %s", buf); LOGE("Command not found: %s", buf);
LOGE("(make 'adb' accessible from your PATH or define its full" LOGE("(make 'adb' accessible from your PATH or define its full"
"path in the ADB environment variable)"); "path in the ADB environment variable)");
show_adb_installation_msg(); show_adb_installation_msg();
break; break;
case SC_PROCESS_SUCCESS: case PROCESS_SUCCESS:
// do nothing // do nothing
break; break;
} }
@ -110,15 +108,16 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) {
free(buf); free(buf);
} }
static sc_pid process_t
adb_execute_p(const char *serial, const char *const adb_cmd[], adb_execute_redirect(const char *serial, const char *const adb_cmd[],
size_t len, unsigned inherit, sc_pipe *pout) { size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout,
pipe_t *pipe_stderr) {
int i; int i;
sc_pid pid; process_t process;
const char **argv = malloc((len + 4) * sizeof(*argv)); const char **argv = malloc((len + 4) * sizeof(*argv));
if (!argv) { if (!argv) {
return SC_PROCESS_NONE; return PROCESS_NONE;
} }
argv[0] = get_adb_command(); argv[0] = get_adb_command();
@ -132,185 +131,149 @@ adb_execute_p(const char *serial, const char *const adb_cmd[],
memcpy(&argv[i], adb_cmd, len * sizeof(const char *)); memcpy(&argv[i], adb_cmd, len * sizeof(const char *));
argv[len + i] = NULL; argv[len + i] = NULL;
enum sc_process_result r = enum process_result r =
sc_process_execute_p(argv, &pid, inherit, NULL, pout, NULL); process_execute_redirect(argv, &process, pipe_stdin, pipe_stdout,
if (r != SC_PROCESS_SUCCESS) { pipe_stderr);
if (r != PROCESS_SUCCESS) {
show_adb_err_msg(r, argv); show_adb_err_msg(r, argv);
pid = SC_PROCESS_NONE; process = PROCESS_NONE;
} }
free(argv); free(argv);
return pid; return process;
} }
sc_pid process_t
adb_execute(const char *serial, const char *const adb_cmd[], size_t len, adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
unsigned inherit) { return adb_execute_redirect(serial, adb_cmd, len, NULL, NULL, NULL);
return adb_execute_p(serial, adb_cmd, len, inherit, NULL);
} }
static sc_pid process_t
adb_exec_forward(const char *serial, uint16_t local_port, adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned inherit) { const char *device_socket_name) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port); sprintf(local, "tcp:%" PRIu16, local_port);
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
const char *const adb_cmd[] = {"forward", local, remote}; const char *const adb_cmd[] = {"forward", local, remote};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), inherit); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
static sc_pid process_t
adb_exec_forward_remove(const char *serial, uint16_t local_port, adb_forward_remove(const char *serial, uint16_t local_port) {
unsigned inherit) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
sprintf(local, "tcp:%" PRIu16, local_port); sprintf(local, "tcp:%" PRIu16, local_port);
const char *const adb_cmd[] = {"forward", "--remove", local}; const char *const adb_cmd[] = {"forward", "--remove", local};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), inherit); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
static sc_pid process_t
adb_exec_reverse(const char *serial, const char *device_socket_name, adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port, unsigned inherit) { 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
sprintf(local, "tcp:%" PRIu16, local_port); sprintf(local, "tcp:%" PRIu16, local_port);
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
const char *const adb_cmd[] = {"reverse", remote, local}; const char *const adb_cmd[] = {"reverse", remote, local};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), inherit); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
static sc_pid process_t
adb_exec_reverse_remove(const char *serial, const char *device_socket_name, adb_reverse_remove(const char *serial, const char *device_socket_name) {
unsigned inherit) {
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
const char *const adb_cmd[] = {"reverse", "--remove", remote}; const char *const adb_cmd[] = {"reverse", "--remove", remote};
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), inherit); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
static sc_pid process_t
adb_exec_push(const char *serial, const char *local, const char *remote, adb_push(const char *serial, const char *local, const char *remote) {
unsigned inherit) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
// Windows will parse the string, so the paths must be quoted // Windows will parse the string, so the paths must be quoted
// (see sys/win/command.c) // (see sys/win/command.c)
local = sc_str_quote(local); local = strquote(local);
if (!local) { if (!local) {
return SC_PROCESS_NONE; return PROCESS_NONE;
} }
remote = sc_str_quote(remote); remote = strquote(remote);
if (!remote) { if (!remote) {
free((void *) local); free((void *) local);
return SC_PROCESS_NONE; return PROCESS_NONE;
} }
#endif #endif
const char *const adb_cmd[] = {"push", local, remote}; const char *const adb_cmd[] = {"push", local, remote};
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), inherit); process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__ #ifdef __WINDOWS__
free((void *) remote); free((void *) remote);
free((void *) local); free((void *) local);
#endif #endif
return pid; return proc;
} }
static sc_pid process_t
adb_exec_install(const char *serial, const char *local, unsigned inherit) { adb_install(const char *serial, const char *local) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
// Windows will parse the string, so the local name must be quoted // Windows will parse the string, so the local name must be quoted
// (see sys/win/command.c) // (see sys/win/command.c)
local = sc_str_quote(local); local = strquote(local);
if (!local) { if (!local) {
return SC_PROCESS_NONE; return PROCESS_NONE;
} }
#endif #endif
const char *const adb_cmd[] = {"install", "-r", local}; const char *const adb_cmd[] = {"install", "-r", local};
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd), inherit); process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__ #ifdef __WINDOWS__
free((void *) local); free((void *) local);
#endif #endif
return pid; return proc;
} }
static sc_pid static ssize_t
adb_exec_get_serialno(unsigned inherit, sc_pipe *pout) { adb_execute_for_output(const char *serial, const char *const adb_cmd[],
const char *const adb_cmd[] = {"get-serialno"}; size_t adb_cmd_len, char *buf, size_t buf_len,
return adb_execute_p(NULL, adb_cmd, ARRAY_LEN(adb_cmd), inherit, pout); const char *name) {
pipe_t pipe_stdout;
process_t proc = adb_execute_redirect(serial, adb_cmd, adb_cmd_len, NULL,
&pipe_stdout, NULL);
ssize_t r = read_pipe_all(pipe_stdout, buf, buf_len);
close_pipe(pipe_stdout);
if (!process_check_success(proc, name, true)) {
return -1;
} }
bool return r;
adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned inherit) {
sc_pid pid =
adb_exec_forward(serial, local_port, device_socket_name, inherit);
return sc_process_check_success_intr(intr, pid, "adb forward", true);
} }
bool static size_t
adb_forward_remove(struct sc_intr *intr, const char *serial, truncate_first_line(char *data, size_t len) {
uint16_t local_port, unsigned inherit) { data[len - 1] = '\0';
sc_pid pid = adb_exec_forward_remove(serial, local_port, inherit); char *eol = strpbrk(data, "\r\n");
return sc_process_check_success_intr(intr, pid, "adb forward --remove", if (eol) {
true); *eol = '\0';
len = eol - data;
} }
return len;
bool
adb_reverse(struct sc_intr *intr, const char *serial,
const char *device_socket_name, uint16_t local_port,
unsigned inherit) {
sc_pid pid =
adb_exec_reverse(serial, device_socket_name, local_port, inherit);
return sc_process_check_success_intr(intr, pid, "adb reverse", true);
}
bool
adb_reverse_remove(struct sc_intr *intr, const char *serial,
const char *device_socket_name, unsigned inherit) {
sc_pid pid = adb_exec_reverse_remove(serial, device_socket_name, inherit);
return sc_process_check_success_intr(intr, pid, "adb reverse --remove",
true);
}
bool
adb_push(struct sc_intr *intr, const char *serial, const char *local,
const char *remote, unsigned inherit) {
sc_pid pid = adb_exec_push(serial, local, remote, inherit);
return sc_process_check_success_intr(intr, pid, "adb push", true);
}
bool
adb_install(struct sc_intr *intr, const char *serial, const char *local,
unsigned inherit) {
sc_pid pid = adb_exec_install(serial, local, inherit);
return sc_process_check_success_intr(intr, pid, "adb install", true);
} }
char * char *
adb_get_serialno(struct sc_intr *intr, unsigned inherit) { adb_get_serialno(void) {
sc_pipe pout;
sc_pid pid = adb_exec_get_serialno(inherit, &pout);
if (pid == SC_PROCESS_NONE) {
LOGE("Could not execute \"adb get-serialno\"");
return NULL;
}
char buf[128]; char buf[128];
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf));
sc_pipe_close(pout);
bool ok = const char *const adb_cmd[] = {"get-serialno"};
sc_process_check_success_intr(intr, pid, "adb get-serialno", true); ssize_t r = adb_execute_for_output(NULL, adb_cmd, ARRAY_LEN(adb_cmd),
if (!ok) { buf, sizeof(buf), "get-serialno");
if (r <= 0) {
return NULL; return NULL;
} }
sc_str_truncate(buf, r, " \r\n"); truncate_first_line(buf, r);
return strdup(buf); return strdup(buf);
} }

View File

@ -6,43 +6,38 @@
#include <stdbool.h> #include <stdbool.h>
#include <inttypes.h> #include <inttypes.h>
#include "util/intr.h" #include "util/process.h"
sc_pid process_t
adb_execute(const char *serial, const char *const adb_cmd[], size_t len, adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
unsigned inherit);
bool process_t
adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, adb_execute_redirect(const char *serial, const char *const adb_cmd[],
const char *device_socket_name, unsigned inherit); size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout,
pipe_t *pipe_stderr);
bool process_t
adb_forward_remove(struct sc_intr *intr, const char *serial, adb_forward(const char *serial, uint16_t local_port,
uint16_t local_port, unsigned inherit); const char *device_socket_name);
bool process_t
adb_reverse(struct sc_intr *intr, const char *serial, adb_forward_remove(const char *serial, uint16_t local_port);
const char *device_socket_name, uint16_t local_port,
unsigned inherit);
bool process_t
adb_reverse_remove(struct sc_intr *intr, const char *serial, adb_reverse(const char *serial, const char *device_socket_name,
const char *device_socket_name, unsigned inherit); uint16_t local_port);
bool process_t
adb_push(struct sc_intr *intr, const char *serial, const char *local, adb_reverse_remove(const char *serial, const char *device_socket_name);
const char *remote, unsigned inherit);
bool process_t
adb_install(struct sc_intr *intr, const char *serial, const char *local, adb_push(const char *serial, const char *local, const char *remote);
unsigned inherit);
/** process_t
* Execute `adb get-serialno` adb_install(const char *serial, const char *local);
*
* Return the result, to be freed by the caller, or NULL on error. // Return the result of "adb get-serialno".
*/
char * char *
adb_get_serialno(struct sc_intr *intr, unsigned inherit); adb_get_serialno(void);
#endif #endif

View File

@ -1,165 +0,0 @@
#include "adb_tunnel.h"
#include <assert.h>
#include "adb.h"
#include "util/log.h"
#include "util/net_intr.h"
#include "util/process_intr.h"
#define SC_SOCKET_NAME "scrcpy"
static bool
listen_on_port(struct sc_intr *intr, sc_socket socket, uint16_t port) {
return net_listen_intr(intr, socket, IPV4_LOCALHOST, port, 1);
}
static bool
enable_tunnel_reverse_any_port(struct sc_adb_tunnel *tunnel,
struct sc_intr *intr, const char *serial,
struct sc_port_range port_range) {
uint16_t port = port_range.first;
for (;;) {
if (!adb_reverse(intr, serial, SC_SOCKET_NAME, port, SC_STDERR)) {
// the command itself failed, it will fail on any port
return false;
}
// At the application level, the device part is "the server" because it
// serves video stream and control. However, at the network level, the
// client listens and the server connects to the client. That way, the
// client can listen before starting the server app, so there is no
// need to try to connect until the server socket is listening on the
// device.
sc_socket server_socket = net_socket();
if (server_socket != SC_SOCKET_NONE) {
bool ok = listen_on_port(intr, server_socket, port);
if (ok) {
// success
tunnel->server_socket = server_socket;
tunnel->local_port = port;
tunnel->enabled = true;
return true;
}
net_close(server_socket);
}
if (sc_intr_is_interrupted(intr)) {
// Stop immediately
return false;
}
// failure, disable tunnel and try another port
if (!adb_reverse_remove(intr, serial, SC_SOCKET_NAME, SC_STDERR)) {
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
}
// check before incrementing to avoid overflow on port 65535
if (port < port_range.last) {
LOGW("Could not listen on port %" PRIu16", retrying on %" PRIu16,
port, (uint16_t) (port + 1));
port++;
continue;
}
if (port_range.first == port_range.last) {
LOGE("Could not listen on port %" PRIu16, port_range.first);
} else {
LOGE("Could not listen on any port in range %" PRIu16 ":%" PRIu16,
port_range.first, port_range.last);
}
return false;
}
}
static bool
enable_tunnel_forward_any_port(struct sc_adb_tunnel *tunnel,
struct sc_intr *intr, const char *serial,
struct sc_port_range port_range) {
tunnel->forward = true;
uint16_t port = port_range.first;
for (;;) {
if (adb_forward(intr, serial, port, SC_SOCKET_NAME, SC_STDERR)) {
// success
tunnel->local_port = port;
tunnel->enabled = true;
return true;
}
if (sc_intr_is_interrupted(intr)) {
// Stop immediately
return false;
}
if (port < port_range.last) {
LOGW("Could not forward port %" PRIu16", retrying on %" PRIu16,
port, (uint16_t) (port + 1));
port++;
continue;
}
if (port_range.first == port_range.last) {
LOGE("Could not forward port %" PRIu16, port_range.first);
} else {
LOGE("Could not forward any port in range %" PRIu16 ":%" PRIu16,
port_range.first, port_range.last);
}
return false;
}
}
void
sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel) {
tunnel->enabled = false;
tunnel->forward = false;
tunnel->server_socket = SC_SOCKET_NONE;
tunnel->local_port = 0;
}
bool
sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
const char *serial, struct sc_port_range port_range,
bool force_adb_forward) {
assert(!tunnel->enabled);
if (!force_adb_forward) {
// Attempt to use "adb reverse"
if (enable_tunnel_reverse_any_port(tunnel, intr, serial, port_range)) {
return true;
}
// if "adb reverse" does not work (e.g. over "adb connect"), it
// fallbacks to "adb forward", so the app socket is the client
LOGW("'adb reverse' failed, fallback to 'adb forward'");
}
return enable_tunnel_forward_any_port(tunnel, intr, serial, port_range);
}
bool
sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
const char *serial) {
assert(tunnel->enabled);
bool ret;
if (tunnel->forward) {
ret = adb_forward_remove(intr, serial, tunnel->local_port, SC_STDERR);
} else {
ret = adb_reverse_remove(intr, serial, SC_SOCKET_NAME, SC_STDERR);
assert(tunnel->server_socket != SC_SOCKET_NONE);
if (!net_close(tunnel->server_socket)) {
LOGW("Could not close server socket");
}
// server_socket is never used anymore
}
// Consider tunnel disabled even if the command failed
tunnel->enabled = false;
return ret;
}

View File

@ -1,47 +0,0 @@
#ifndef SC_ADB_TUNNEL_H
#define SC_ADB_TUNNEL_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
#include "options.h"
#include "util/intr.h"
#include "util/net.h"
struct sc_adb_tunnel {
bool enabled;
bool forward; // use "adb forward" instead of "adb reverse"
sc_socket server_socket; // only used if !forward
uint16_t local_port;
};
/**
* Initialize the adb tunnel struct to default values
*/
void
sc_adb_tunnel_init(struct sc_adb_tunnel *tunnel);
/**
* Open a tunnel
*
* Blocking calls may be interrupted asynchronously via `intr`.
*
* If `force_adb_forward` is not set, then attempts to set up an "adb reverse"
* tunnel first. Only if it fails (typical on old Android version connected via
* TCP/IP), use "adb forward".
*/
bool
sc_adb_tunnel_open(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
const char *serial, struct sc_port_range port_range,
bool force_adb_forward);
/**
* Close the tunnel
*/
bool
sc_adb_tunnel_close(struct sc_adb_tunnel *tunnel, struct sc_intr *intr,
const char *serial);
#endif

View File

@ -14,7 +14,7 @@
#define DEFAULT_TIMEOUT 1000 #define DEFAULT_TIMEOUT 1000
static void static void
sc_hid_event_log(const struct sc_hid_event *event) { hid_event_log(const struct hid_event *event) {
// HID Event: [00] FF FF FF FF... // HID Event: [00] FF FF FF FF...
assert(event->size); assert(event->size);
unsigned buffer_size = event->size * 3 + 1; unsigned buffer_size = event->size * 3 + 1;
@ -25,25 +25,16 @@ sc_hid_event_log(const struct sc_hid_event *event) {
for (unsigned i = 0; i < event->size; ++i) { for (unsigned i = 0; i < event->size; ++i) {
snprintf(buffer + i * 3, 4, " %02x", event->buffer[i]); snprintf(buffer + i * 3, 4, " %02x", event->buffer[i]);
} }
LOGV("HID Event: [%d]%s", event->accessory_id, buffer); LOGV("HID Event: [%d]%s", event->from_accessory_id, buffer);
free(buffer); free(buffer);
} }
void static void
sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id, hid_event_destroy(struct hid_event *event) {
unsigned char *buffer, uint16_t buffer_size) { free(event->buffer);
hid_event->accessory_id = accessory_id;
hid_event->buffer = buffer;
hid_event->size = buffer_size;
hid_event->delay = 0;
} }
void static void
sc_hid_event_destroy(struct sc_hid_event *hid_event) {
free(hid_event->buffer);
}
static inline void
log_libusb_error(enum libusb_error errcode) { log_libusb_error(enum libusb_error errcode) {
LOGW("libusb error: %s", libusb_strerror(errcode)); LOGW("libusb error: %s", libusb_strerror(errcode));
} }
@ -82,7 +73,7 @@ accept_device(libusb_device *device, const char *serial) {
} }
static libusb_device * static libusb_device *
sc_aoa_find_usb_device(const char *serial) { aoa_find_usb_device(const char *serial) {
if (!serial) { if (!serial) {
return NULL; return NULL;
} }
@ -108,7 +99,7 @@ sc_aoa_find_usb_device(const char *serial) {
} }
static int static int
sc_aoa_open_usb_handle(libusb_device *device, libusb_device_handle **handle) { aoa_open_usb_handle(libusb_device *device, libusb_device_handle **handle) {
int result = libusb_open(device, handle); int result = libusb_open(device, handle);
if (result < 0) { if (result < 0) {
log_libusb_error((enum libusb_error) result); log_libusb_error((enum libusb_error) result);
@ -118,7 +109,7 @@ sc_aoa_open_usb_handle(libusb_device *device, libusb_device_handle **handle) {
} }
bool bool
sc_aoa_init(struct sc_aoa *aoa, const char *serial) { aoa_init(struct aoa *aoa, const char *serial) {
cbuf_init(&aoa->queue); cbuf_init(&aoa->queue);
if (!sc_mutex_init(&aoa->mutex)) { if (!sc_mutex_init(&aoa->mutex)) {
@ -136,7 +127,7 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial) {
return false; return false;
} }
aoa->usb_device = sc_aoa_find_usb_device(serial); aoa->usb_device = aoa_find_usb_device(serial);
if (!aoa->usb_device) { if (!aoa->usb_device) {
LOGW("USB device of serial %s not found", serial); LOGW("USB device of serial %s not found", serial);
libusb_exit(aoa->usb_context); libusb_exit(aoa->usb_context);
@ -145,7 +136,7 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial) {
return false; return false;
} }
if (sc_aoa_open_usb_handle(aoa->usb_device, &aoa->usb_handle) < 0) { if (aoa_open_usb_handle(aoa->usb_device, &aoa->usb_handle) < 0) {
LOGW("Open USB handle failed"); LOGW("Open USB handle failed");
libusb_unref_device(aoa->usb_device); libusb_unref_device(aoa->usb_device);
libusb_exit(aoa->usb_context); libusb_exit(aoa->usb_context);
@ -160,11 +151,11 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial) {
} }
void void
sc_aoa_destroy(struct sc_aoa *aoa) { aoa_destroy(struct aoa *aoa) {
// Destroy remaining events // Destroy remaining events
struct sc_hid_event event; struct hid_event event;
while (cbuf_take(&aoa->queue, &event)) { while (cbuf_take(&aoa->queue, &event)) {
sc_hid_event_destroy(&event); hid_event_destroy(&event);
} }
libusb_close(aoa->usb_handle); libusb_close(aoa->usb_handle);
@ -175,7 +166,7 @@ sc_aoa_destroy(struct sc_aoa *aoa) {
} }
static bool static bool
sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id, aoa_register_hid(struct aoa *aoa, uint16_t accessory_id,
uint16_t report_desc_size) { uint16_t report_desc_size) {
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR; uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
uint8_t request = ACCESSORY_REGISTER_HID; uint8_t request = ACCESSORY_REGISTER_HID;
@ -198,7 +189,7 @@ sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id,
} }
static bool static bool
sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id, aoa_set_hid_report_desc(struct aoa *aoa, uint16_t accessory_id,
const unsigned char *report_desc, const unsigned char *report_desc,
uint16_t report_desc_size) { uint16_t report_desc_size) {
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR; uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
@ -234,17 +225,17 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id,
} }
bool bool
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id, aoa_setup_hid(struct aoa *aoa, uint16_t accessory_id,
const unsigned char *report_desc, uint16_t report_desc_size) { const unsigned char *report_desc, uint16_t report_desc_size) {
bool ok = sc_aoa_register_hid(aoa, accessory_id, report_desc_size); bool ok = aoa_register_hid(aoa, accessory_id, report_desc_size);
if (!ok) { if (!ok) {
return false; return false;
} }
ok = sc_aoa_set_hid_report_desc(aoa, accessory_id, report_desc, ok = aoa_set_hid_report_desc(aoa, accessory_id, report_desc,
report_desc_size); report_desc_size);
if (!ok) { if (!ok) {
if (!sc_aoa_unregister_hid(aoa, accessory_id)) { if (!aoa_unregister_hid(aoa, accessory_id)) {
LOGW("Could not unregister HID"); LOGW("Could not unregister HID");
} }
return false; return false;
@ -254,13 +245,13 @@ sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
} }
static bool static bool
sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) { aoa_send_hid_event(struct aoa *aoa, const struct hid_event *event) {
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR; uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
uint8_t request = ACCESSORY_SEND_HID_EVENT; uint8_t request = ACCESSORY_SEND_HID_EVENT;
// <https://source.android.com/devices/accessories/aoa2.html#hid-support> // <https://source.android.com/devices/accessories/aoa2.html#hid-support>
// value (arg0): accessory assigned ID for the HID device // value (arg0): accessory assigned ID for the HID device
// index (arg1): 0 (unused) // index (arg1): 0 (unused)
uint16_t value = event->accessory_id; uint16_t value = event->from_accessory_id;
uint16_t index = 0; uint16_t index = 0;
unsigned char *buffer = event->buffer; unsigned char *buffer = event->buffer;
uint16_t length = event->size; uint16_t length = event->size;
@ -276,7 +267,7 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
} }
bool bool
sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) { aoa_unregister_hid(struct aoa *aoa, const uint16_t accessory_id) {
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR; uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
uint8_t request = ACCESSORY_UNREGISTER_HID; uint8_t request = ACCESSORY_UNREGISTER_HID;
// <https://source.android.com/devices/accessories/aoa2.html#hid-support> // <https://source.android.com/devices/accessories/aoa2.html#hid-support>
@ -298,11 +289,8 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) {
} }
bool bool
sc_aoa_push_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) { aoa_push_hid_event(struct aoa *aoa, const struct hid_event *event) {
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) { hid_event_log(event);
sc_hid_event_log(event);
}
sc_mutex_lock(&aoa->mutex); sc_mutex_lock(&aoa->mutex);
bool was_empty = cbuf_is_empty(&aoa->queue); bool was_empty = cbuf_is_empty(&aoa->queue);
bool res = cbuf_push(&aoa->queue, *event); bool res = cbuf_push(&aoa->queue, *event);
@ -315,7 +303,7 @@ sc_aoa_push_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
static int static int
run_aoa_thread(void *data) { run_aoa_thread(void *data) {
struct sc_aoa *aoa = data; struct aoa *aoa = data;
for (;;) { for (;;) {
sc_mutex_lock(&aoa->mutex); sc_mutex_lock(&aoa->mutex);
@ -327,30 +315,14 @@ run_aoa_thread(void *data) {
sc_mutex_unlock(&aoa->mutex); sc_mutex_unlock(&aoa->mutex);
break; break;
} }
struct sc_hid_event event; struct hid_event event;
bool non_empty = cbuf_take(&aoa->queue, &event); bool non_empty = cbuf_take(&aoa->queue, &event);
assert(non_empty); assert(non_empty);
(void) non_empty; (void) non_empty;
assert(event.delay >= 0);
if (event.delay) {
// Wait during the specified delay before injecting the HID event
sc_tick deadline = sc_tick_now() + event.delay;
bool timed_out = false;
while (!aoa->stopped && !timed_out) {
timed_out = !sc_cond_timedwait(&aoa->event_cond, &aoa->mutex,
deadline);
}
if (aoa->stopped) {
sc_mutex_unlock(&aoa->mutex);
break;
}
}
sc_mutex_unlock(&aoa->mutex); sc_mutex_unlock(&aoa->mutex);
bool ok = sc_aoa_send_hid_event(aoa, &event); bool ok = aoa_send_hid_event(aoa, &event);
sc_hid_event_destroy(&event); hid_event_destroy(&event);
if (!ok) { if (!ok) {
LOGW("Could not send HID event to USB device"); LOGW("Could not send HID event to USB device");
} }
@ -359,7 +331,7 @@ run_aoa_thread(void *data) {
} }
bool bool
sc_aoa_start(struct sc_aoa *aoa) { aoa_start(struct aoa *aoa) {
LOGD("Starting AOA thread"); LOGD("Starting AOA thread");
bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "aoa_thread", aoa); bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "aoa_thread", aoa);
@ -372,7 +344,7 @@ sc_aoa_start(struct sc_aoa *aoa) {
} }
void void
sc_aoa_stop(struct sc_aoa *aoa) { aoa_stop(struct aoa *aoa) {
sc_mutex_lock(&aoa->mutex); sc_mutex_lock(&aoa->mutex);
aoa->stopped = true; aoa->stopped = true;
sc_cond_signal(&aoa->event_cond); sc_cond_signal(&aoa->event_cond);
@ -380,6 +352,6 @@ sc_aoa_stop(struct sc_aoa *aoa) {
} }
void void
sc_aoa_join(struct sc_aoa *aoa) { aoa_join(struct aoa *aoa) {
sc_thread_join(&aoa->thread, NULL); sc_thread_join(&aoa->thread, NULL);
} }

View File

@ -1,33 +1,24 @@
#ifndef SC_AOA_HID_H #ifndef AOA_HID_H
#define SC_AOA_HID_H #define AOA_HID_H
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <libusb-1.0/libusb.h> #include <libusb-1.0/libusb.h>
#include "scrcpy.h"
#include "util/cbuf.h" #include "util/cbuf.h"
#include "util/thread.h" #include "util/thread.h"
#include "util/tick.h"
struct sc_hid_event { struct hid_event {
uint16_t accessory_id; uint16_t from_accessory_id;
unsigned char *buffer; unsigned char *buffer;
uint16_t size; uint16_t size;
sc_tick delay;
}; };
// Takes ownership of buffer struct hid_event_queue CBUF(struct hid_event, 64);
void
sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id,
unsigned char *buffer, uint16_t buffer_size);
void struct aoa {
sc_hid_event_destroy(struct sc_hid_event *hid_event);
struct sc_hid_event_queue CBUF(struct sc_hid_event, 64);
struct sc_aoa {
libusb_context *usb_context; libusb_context *usb_context;
libusb_device *usb_device; libusb_device *usb_device;
libusb_device_handle *usb_handle; libusb_device_handle *usb_handle;
@ -35,32 +26,32 @@ struct sc_aoa {
sc_mutex mutex; sc_mutex mutex;
sc_cond event_cond; sc_cond event_cond;
bool stopped; bool stopped;
struct sc_hid_event_queue queue; struct hid_event_queue queue;
}; };
bool bool
sc_aoa_init(struct sc_aoa *aoa, const char *serial); aoa_init(struct aoa *aoa, const char *serial);
void void
sc_aoa_destroy(struct sc_aoa *aoa); aoa_destroy(struct aoa *aoa);
bool bool
sc_aoa_start(struct sc_aoa *aoa); aoa_start(struct aoa *aoa);
void void
sc_aoa_stop(struct sc_aoa *aoa); aoa_stop(struct aoa *aoa);
void void
sc_aoa_join(struct sc_aoa *aoa); aoa_join(struct aoa *aoa);
bool bool
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id, aoa_setup_hid(struct aoa *aoa, uint16_t accessory_id,
const unsigned char *report_desc, uint16_t report_desc_size); const unsigned char *report_desc, uint16_t report_desc_size);
bool bool
sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id); aoa_unregister_hid(struct aoa *aoa, uint16_t accessory_id);
bool bool
sc_aoa_push_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event); aoa_push_hid_event(struct aoa *aoa, const struct hid_event *event);
#endif #endif

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
#include <stdbool.h> #include <stdbool.h>
#include "options.h" #include "scrcpy.h"
struct scrcpy_cli_args { struct scrcpy_cli_args {
struct scrcpy_options opts; struct scrcpy_options opts;

View File

@ -26,7 +26,7 @@ struct sc_clock_point {
* array. * array.
* *
* To estimate the slope, it splits the last SC_CLOCK_RANGE points into two * To estimate the slope, it splits the last SC_CLOCK_RANGE points into two
* sets of SC_CLOCK_RANGE/2 points, and computes their centroid ("average * sets of SC_CLOCK_RANGE/2 points, and compute their centroid ("average
* point"). The slope of the estimated affine function is that of the line * point"). The slope of the estimated affine function is that of the line
* passing through these two points. * passing through these two points.
* *

View File

@ -43,11 +43,6 @@
# define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP # define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
#endif #endif
#if SDL_VERSION_ATLEAST(2, 0, 6)
// <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc>
# define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
#endif
#if SDL_VERSION_ATLEAST(2, 0, 8) #if SDL_VERSION_ATLEAST(2, 0, 8)
// <https://hg.libsdl.org/SDL/rev/dfde5d3f9781> // <https://hg.libsdl.org/SDL/rev/dfde5d3f9781>
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR # define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR

View File

@ -7,7 +7,7 @@
#include "util/buffer_util.h" #include "util/buffer_util.h"
#include "util/log.h" #include "util/log.h"
#include "util/str.h" #include "util/str_util.h"
/** /**
* Map an enum value to a string based on an array, without crashing on an * Map an enum value to a string based on an array, without crashing on an
@ -56,7 +56,7 @@ static const char *const screen_power_mode_labels[] = {
}; };
static void static void
write_position(uint8_t *buf, const struct sc_position *position) { write_position(uint8_t *buf, const struct position *position) {
buffer_write32be(&buf[0], position->point.x); buffer_write32be(&buf[0], position->point.x);
buffer_write32be(&buf[4], position->point.y); buffer_write32be(&buf[4], position->point.y);
buffer_write16be(&buf[8], position->screen_size.width); buffer_write16be(&buf[8], position->screen_size.width);
@ -66,7 +66,7 @@ write_position(uint8_t *buf, const struct sc_position *position) {
// write length (2 bytes) + string (non nul-terminated) // write length (2 bytes) + string (non nul-terminated)
static size_t static size_t
write_string(const char *utf8, size_t max_len, unsigned char *buf) { write_string(const char *utf8, size_t max_len, unsigned char *buf) {
size_t len = sc_str_utf8_truncation_index(utf8, max_len); size_t len = utf8_truncation_index(utf8, max_len);
buffer_write32be(buf, len); buffer_write32be(buf, len);
memcpy(&buf[4], utf8, len); memcpy(&buf[4], utf8, len);
return 4 + len; return 4 + len;

View File

@ -57,11 +57,11 @@ struct control_msg {
enum android_motionevent_action action; enum android_motionevent_action action;
enum android_motionevent_buttons buttons; enum android_motionevent_buttons buttons;
uint64_t pointer_id; uint64_t pointer_id;
struct sc_position position; struct position position;
float pressure; float pressure;
} inject_touch_event; } inject_touch_event;
struct { struct {
struct sc_position position; struct position position;
int32_t hscroll; int32_t hscroll;
int32_t vscroll; int32_t vscroll;
} inject_scroll_event; } inject_scroll_event;

View File

@ -5,7 +5,7 @@
#include "util/log.h" #include "util/log.h"
bool bool
controller_init(struct controller *controller, sc_socket control_socket) { controller_init(struct controller *controller, socket_t control_socket) {
cbuf_init(&controller->queue); cbuf_init(&controller->queue);
bool ok = receiver_init(&controller->receiver, control_socket); bool ok = receiver_init(&controller->receiver, control_socket);
@ -63,14 +63,14 @@ controller_push_msg(struct controller *controller,
} }
static bool static bool
process_msg(struct controller *controller, const struct control_msg *msg) { process_msg(struct controller *controller,
const struct control_msg *msg) {
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE]; static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
size_t length = control_msg_serialize(msg, serialized_msg); size_t length = control_msg_serialize(msg, serialized_msg);
if (!length) { if (!length) {
return false; return false;
} }
ssize_t w = ssize_t w = net_send_all(controller->control_socket, serialized_msg, length);
net_send_all(controller->control_socket, serialized_msg, length);
return (size_t) w == length; return (size_t) w == length;
} }

View File

@ -14,7 +14,7 @@
struct control_msg_queue CBUF(struct control_msg, 64); struct control_msg_queue CBUF(struct control_msg, 64);
struct controller { struct controller {
sc_socket control_socket; socket_t control_socket;
sc_thread thread; sc_thread thread;
sc_mutex mutex; sc_mutex mutex;
sc_cond msg_cond; sc_cond msg_cond;
@ -24,7 +24,7 @@ struct controller {
}; };
bool bool
controller_init(struct controller *controller, sc_socket control_socket); controller_init(struct controller *controller, socket_t control_socket);
void void
controller_destroy(struct controller *controller); controller_destroy(struct controller *controller);

View File

@ -3,22 +3,22 @@
#include <stdint.h> #include <stdint.h>
struct sc_size { struct size {
uint16_t width; uint16_t width;
uint16_t height; uint16_t height;
}; };
struct sc_point { struct point {
int32_t x; int32_t x;
int32_t y; int32_t y;
}; };
struct sc_position { struct position {
// The video screen size may be different from the real device screen size, // The video screen size may be different from the real device screen size,
// so store to which size the absolute position apply, to scale it // so store to which size the absolute position apply, to scale it
// accordingly. // accordingly.
struct sc_size screen_size; struct size screen_size;
struct sc_point point; struct point point;
}; };
#endif #endif

View File

@ -1,4 +1,2 @@
#define EVENT_NEW_FRAME SDL_USEREVENT #define EVENT_NEW_FRAME SDL_USEREVENT
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1) #define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
#define EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
#define EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)

View File

@ -5,7 +5,6 @@
#include "adb.h" #include "adb.h"
#include "util/log.h" #include "util/log.h"
#include "util/process_intr.h"
#define DEFAULT_PUSH_TARGET "/sdcard/Download/" #define DEFAULT_PUSH_TARGET "/sdcard/Download/"
@ -17,7 +16,6 @@ file_handler_request_destroy(struct file_handler_request *req) {
bool bool
file_handler_init(struct file_handler *file_handler, const char *serial, file_handler_init(struct file_handler *file_handler, const char *serial,
const char *push_target) { const char *push_target) {
assert(serial);
cbuf_init(&file_handler->queue); cbuf_init(&file_handler->queue);
@ -32,26 +30,23 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
return false; return false;
} }
ok = sc_intr_init(&file_handler->intr); if (serial) {
if (!ok) {
LOGE("Could not create intr");
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
}
file_handler->serial = strdup(serial); file_handler->serial = strdup(serial);
if (!file_handler->serial) { if (!file_handler->serial) {
LOGE("Could not strdup serial"); LOGW("Could not strdup serial");
sc_intr_destroy(&file_handler->intr);
sc_cond_destroy(&file_handler->event_cond); sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex); sc_mutex_destroy(&file_handler->mutex);
return false; return false;
} }
} else {
file_handler->serial = NULL;
}
// lazy initialization // lazy initialization
file_handler->initialized = false; file_handler->initialized = false;
file_handler->stopped = false; file_handler->stopped = false;
file_handler->current_process = PROCESS_NONE;
file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET; file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET;
@ -62,7 +57,6 @@ void
file_handler_destroy(struct file_handler *file_handler) { file_handler_destroy(struct file_handler *file_handler) {
sc_cond_destroy(&file_handler->event_cond); sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex); sc_mutex_destroy(&file_handler->mutex);
sc_intr_destroy(&file_handler->intr);
free(file_handler->serial); free(file_handler->serial);
struct file_handler_request req; struct file_handler_request req;
@ -71,6 +65,16 @@ file_handler_destroy(struct file_handler *file_handler) {
} }
} }
static process_t
install_apk(const char *serial, const char *file) {
return adb_install(serial, file);
}
static process_t
push_file(const char *serial, const char *file, const char *push_target) {
return adb_push(serial, file, push_target);
}
bool bool
file_handler_request(struct file_handler *file_handler, file_handler_request(struct file_handler *file_handler,
file_handler_action_t action, char *file) { file_handler_action_t action, char *file) {
@ -102,16 +106,10 @@ file_handler_request(struct file_handler *file_handler,
static int static int
run_file_handler(void *data) { run_file_handler(void *data) {
struct file_handler *file_handler = data; struct file_handler *file_handler = data;
struct sc_intr *intr = &file_handler->intr;
const char *serial = file_handler->serial;
assert(serial);
const char *push_target = file_handler->push_target;
assert(push_target);
for (;;) { for (;;) {
sc_mutex_lock(&file_handler->mutex); sc_mutex_lock(&file_handler->mutex);
file_handler->current_process = PROCESS_NONE;
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) { while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
sc_cond_wait(&file_handler->event_cond, &file_handler->mutex); sc_cond_wait(&file_handler->event_cond, &file_handler->mutex);
} }
@ -124,28 +122,43 @@ run_file_handler(void *data) {
bool non_empty = cbuf_take(&file_handler->queue, &req); bool non_empty = cbuf_take(&file_handler->queue, &req);
assert(non_empty); assert(non_empty);
(void) non_empty; (void) non_empty;
process_t process;
if (req.action == ACTION_INSTALL_APK) {
LOGI("Installing %s...", req.file);
process = install_apk(file_handler->serial, req.file);
} else {
LOGI("Pushing %s...", req.file);
process = push_file(file_handler->serial, req.file,
file_handler->push_target);
}
file_handler->current_process = process;
sc_mutex_unlock(&file_handler->mutex); sc_mutex_unlock(&file_handler->mutex);
if (req.action == ACTION_INSTALL_APK) { if (req.action == ACTION_INSTALL_APK) {
LOGI("Installing %s...", req.file); if (process_check_success(process, "adb install", false)) {
bool ok =
adb_install(intr, serial, req.file, SC_STDOUT | SC_STDERR);
if (ok) {
LOGI("%s successfully installed", req.file); LOGI("%s successfully installed", req.file);
} else { } else {
LOGE("Failed to install %s", req.file); LOGE("Failed to install %s", req.file);
} }
} else { } else {
LOGI("Pushing %s...", req.file); if (process_check_success(process, "adb push", false)) {
bool ok = adb_push(intr, serial, req.file, push_target, LOGI("%s successfully pushed to %s", req.file,
SC_STDOUT | SC_STDERR); file_handler->push_target);
if (ok) {
LOGI("%s successfully pushed to %s", req.file, push_target);
} else { } else {
LOGE("Failed to push %s to %s", req.file, push_target); LOGE("Failed to push %s to %s", req.file,
file_handler->push_target);
} }
} }
sc_mutex_lock(&file_handler->mutex);
// Close the process (it is necessary already terminated)
// Execute this call with mutex locked to avoid race conditions with
// file_handler_stop()
process_close(file_handler->current_process);
file_handler->current_process = PROCESS_NONE;
sc_mutex_unlock(&file_handler->mutex);
file_handler_request_destroy(&req); file_handler_request_destroy(&req);
} }
return 0; return 0;
@ -170,7 +183,11 @@ file_handler_stop(struct file_handler *file_handler) {
sc_mutex_lock(&file_handler->mutex); sc_mutex_lock(&file_handler->mutex);
file_handler->stopped = true; file_handler->stopped = true;
sc_cond_signal(&file_handler->event_cond); sc_cond_signal(&file_handler->event_cond);
sc_intr_interrupt(&file_handler->intr); if (file_handler->current_process != PROCESS_NONE) {
if (!process_terminate(file_handler->current_process)) {
LOGW("Could not terminate push/install process");
}
}
sc_mutex_unlock(&file_handler->mutex); sc_mutex_unlock(&file_handler->mutex);
} }

View File

@ -8,7 +8,6 @@
#include "adb.h" #include "adb.h"
#include "util/cbuf.h" #include "util/cbuf.h"
#include "util/thread.h" #include "util/thread.h"
#include "util/intr.h"
typedef enum { typedef enum {
ACTION_INSTALL_APK, ACTION_INSTALL_APK,
@ -30,9 +29,8 @@ struct file_handler {
sc_cond event_cond; sc_cond event_cond;
bool stopped; bool stopped;
bool initialized; bool initialized;
process_t current_process;
struct file_handler_request_queue queue; struct file_handler_request_queue queue;
struct sc_intr intr;
}; };
bool bool

View File

@ -6,19 +6,20 @@
#include "util/log.h" #include "util/log.h"
/** Downcast key processor to hid_keyboard */ /** Downcast key processor to hid_keyboard */
#define DOWNCAST(KP) container_of(KP, struct sc_hid_keyboard, key_processor) #define DOWNCAST(KP) \
container_of(KP, struct hid_keyboard, key_processor)
#define HID_KEYBOARD_ACCESSORY_ID 1 #define HID_KEYBOARD_ACCESSORY_ID 1
#define HID_MODIFIER_NONE 0x00 #define HID_KEYBOARD_MODIFIER_NONE 0x00
#define HID_MODIFIER_LEFT_CONTROL (1 << 0) #define HID_KEYBOARD_MODIFIER_LEFT_CONTROL (1 << 0)
#define HID_MODIFIER_LEFT_SHIFT (1 << 1) #define HID_KEYBOARD_MODIFIER_LEFT_SHIFT (1 << 1)
#define HID_MODIFIER_LEFT_ALT (1 << 2) #define HID_KEYBOARD_MODIFIER_LEFT_ALT (1 << 2)
#define HID_MODIFIER_LEFT_GUI (1 << 3) #define HID_KEYBOARD_MODIFIER_LEFT_GUI (1 << 3)
#define HID_MODIFIER_RIGHT_CONTROL (1 << 4) #define HID_KEYBOARD_MODIFIER_RIGHT_CONTROL (1 << 4)
#define HID_MODIFIER_RIGHT_SHIFT (1 << 5) #define HID_KEYBOARD_MODIFIER_RIGHT_SHIFT (1 << 5)
#define HID_MODIFIER_RIGHT_ALT (1 << 6) #define HID_KEYBOARD_MODIFIER_RIGHT_ALT (1 << 6)
#define HID_MODIFIER_RIGHT_GUI (1 << 7) #define HID_KEYBOARD_MODIFIER_RIGHT_GUI (1 << 7)
#define HID_KEYBOARD_INDEX_MODIFIER 0 #define HID_KEYBOARD_INDEX_MODIFIER 0
#define HID_KEYBOARD_INDEX_KEYS 2 #define HID_KEYBOARD_INDEX_KEYS 2
@ -29,8 +30,11 @@
#define HID_KEYBOARD_MAX_KEYS 6 #define HID_KEYBOARD_MAX_KEYS 6
#define HID_KEYBOARD_EVENT_SIZE (2 + HID_KEYBOARD_MAX_KEYS) #define HID_KEYBOARD_EVENT_SIZE (2 + HID_KEYBOARD_MAX_KEYS)
#define HID_RESERVED 0x00 #define HID_KEYBOARD_RESERVED 0x00
#define HID_ERROR_ROLL_OVER 0x01 #define HID_KEYBOARD_ERROR_ROLL_OVER 0x01
#define SC_SCANCODE_CAPSLOCK 57
#define SC_SCANCODE_NUMLOCK 83
/** /**
* For HID over AOAv2, only report descriptor is needed. * For HID over AOAv2, only report descriptor is needed.
@ -110,11 +114,11 @@ static const unsigned char keyboard_report_desc[] = {
// Usage Minimum (0) // Usage Minimum (0)
0x19, 0x00, 0x19, 0x00,
// Usage Maximum (101) // Usage Maximum (101)
0x29, SC_HID_KEYBOARD_KEYS - 1, 0x29, HID_KEYBOARD_KEYS - 1,
// Logical Minimum (0) // Logical Minimum (0)
0x15, 0x00, 0x15, 0x00,
// Logical Maximum(101) // Logical Maximum(101)
0x25, SC_HID_KEYBOARD_KEYS - 1, 0x25, HID_KEYBOARD_KEYS - 1,
// Report Size (8) // Report Size (8)
0x75, 0x08, 0x75, 0x08,
// Report Count (6) // Report Count (6)
@ -126,49 +130,129 @@ static const unsigned char keyboard_report_desc[] = {
0xC0 0xC0
}; };
static unsigned char *
create_hid_keyboard_event(void) {
unsigned char *buffer = malloc(HID_KEYBOARD_EVENT_SIZE);
if (!buffer) {
return NULL;
}
buffer[HID_KEYBOARD_INDEX_MODIFIER] = HID_KEYBOARD_MODIFIER_NONE;
buffer[1] = HID_KEYBOARD_RESERVED;
memset(&buffer[HID_KEYBOARD_INDEX_KEYS], 0, HID_KEYBOARD_MAX_KEYS);
return buffer;
}
static unsigned char static unsigned char
sdl_keymod_to_hid_modifiers(SDL_Keymod mod) { sdl_keymod_to_hid_modifiers(SDL_Keymod mod) {
unsigned char modifiers = HID_MODIFIER_NONE; unsigned char modifiers = HID_KEYBOARD_MODIFIER_NONE;
if (mod & KMOD_LCTRL) { if (mod & KMOD_LCTRL) {
modifiers |= HID_MODIFIER_LEFT_CONTROL; modifiers |= HID_KEYBOARD_MODIFIER_LEFT_CONTROL;
} }
if (mod & KMOD_LSHIFT) { if (mod & KMOD_LSHIFT) {
modifiers |= HID_MODIFIER_LEFT_SHIFT; modifiers |= HID_KEYBOARD_MODIFIER_LEFT_SHIFT;
} }
if (mod & KMOD_LALT) { if (mod & KMOD_LALT) {
modifiers |= HID_MODIFIER_LEFT_ALT; modifiers |= HID_KEYBOARD_MODIFIER_LEFT_ALT;
} }
if (mod & KMOD_LGUI) { if (mod & KMOD_LGUI) {
modifiers |= HID_MODIFIER_LEFT_GUI; modifiers |= HID_KEYBOARD_MODIFIER_LEFT_GUI;
} }
if (mod & KMOD_RCTRL) { if (mod & KMOD_RCTRL) {
modifiers |= HID_MODIFIER_RIGHT_CONTROL; modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_CONTROL;
} }
if (mod & KMOD_RSHIFT) { if (mod & KMOD_RSHIFT) {
modifiers |= HID_MODIFIER_RIGHT_SHIFT; modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_SHIFT;
} }
if (mod & KMOD_RALT) { if (mod & KMOD_RALT) {
modifiers |= HID_MODIFIER_RIGHT_ALT; modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_ALT;
} }
if (mod & KMOD_RGUI) { if (mod & KMOD_RGUI) {
modifiers |= HID_MODIFIER_RIGHT_GUI; modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_GUI;
} }
return modifiers; return modifiers;
} }
static bool static bool
sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) { send_mod_lock_state(struct hid_keyboard *kb, unsigned lock_mod) {
unsigned char *buffer = malloc(HID_KEYBOARD_EVENT_SIZE); assert(!(lock_mod & ~SC_MOD_MASK));
if (!buffer) { if (!lock_mod) {
return false; // Nothing to do
return true;
} }
buffer[HID_KEYBOARD_INDEX_MODIFIER] = HID_MODIFIER_NONE; struct hid_event hid_event;
buffer[1] = HID_RESERVED; hid_event.from_accessory_id = HID_KEYBOARD_ACCESSORY_ID;
memset(&buffer[HID_KEYBOARD_INDEX_KEYS], 0, HID_KEYBOARD_MAX_KEYS); hid_event.buffer = create_hid_keyboard_event();
if (!hid_event.buffer) {
return false;
}
hid_event.size = HID_KEYBOARD_EVENT_SIZE;
unsigned i = 0;
if (lock_mod & SC_MOD_CAPSLOCK) {
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
++i;
}
if (lock_mod & SC_MOD_NUMLOCK) {
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
++i;
}
// for (int i = 0; i < HID_KEYBOARD_EVENT_SIZE; ++i)
// printf("%02x ", hid_event->buffer[i]);
// printf("\n");
if (!aoa_push_hid_event(kb->aoa, &hid_event)) {
LOGW("Could request HID event");
}
return true;
}
static bool
convert_hid_keyboard_event(struct hid_keyboard *kb, struct hid_event *hid_event,
const SDL_KeyboardEvent *event) {
hid_event->buffer = create_hid_keyboard_event();
if (!hid_event->buffer) {
return false;
}
hid_event->size = HID_KEYBOARD_EVENT_SIZE;
unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->keysym.mod);
SDL_Scancode scancode = event->keysym.scancode;
assert(scancode >= 0);
if (scancode < HID_KEYBOARD_KEYS) {
// Pressed is true and released is false
kb->keys[scancode] = (event->type == SDL_KEYDOWN);
LOGV("keys[%02x] = %s", scancode,
kb->keys[scancode] ? "true" : "false");
}
hid_event->buffer[HID_KEYBOARD_INDEX_MODIFIER] = modifiers;
// Re-calculate pressed keys every time
int keys_pressed_count = 0;
for (int i = 0; i < HID_KEYBOARD_KEYS; ++i) {
if (kb->keys[i]) {
// USB HID protocol says that if keys exceeds report count, a
// phantom state should be reported
if (keys_pressed_count >= HID_KEYBOARD_MAX_KEYS) {
// Pantom state:
// - Modifiers
// - Reserved
// - ErrorRollOver * HID_KEYBOARD_MAX_KEYS
memset(&hid_event->buffer[HID_KEYBOARD_INDEX_KEYS],
HID_KEYBOARD_ERROR_ROLL_OVER, HID_KEYBOARD_MAX_KEYS);
return true;
}
hid_event->buffer[HID_KEYBOARD_INDEX_KEYS + keys_pressed_count] = i;
++keys_pressed_count;
}
}
sc_hid_event_init(hid_event, HID_KEYBOARD_ACCESSORY_ID, buffer,
HID_KEYBOARD_EVENT_SIZE);
return true; return true;
} }
@ -178,104 +262,30 @@ scancode_is_modifier(SDL_Scancode scancode) {
} }
static bool static bool
convert_hid_keyboard_event(struct sc_hid_keyboard *kb, hid_keyboard_convert_event(struct hid_keyboard *kb, struct hid_event *hid_event,
struct sc_hid_event *hid_event,
const SDL_KeyboardEvent *event) { const SDL_KeyboardEvent *event) {
LOGV(
"Type: %s, Repeat: %s, Modifiers: %02x, Key: %02x",
event->type == SDL_KEYDOWN ? "down" : "up",
event->repeat != 0 ? "true" : "false",
sdl_keymod_to_hid_modifiers(event->keysym.mod),
event->keysym.scancode
);
hid_event->from_accessory_id = HID_KEYBOARD_ACCESSORY_ID;
SDL_Scancode scancode = event->keysym.scancode; SDL_Scancode scancode = event->keysym.scancode;
assert(scancode >= 0); assert(scancode >= 0);
// SDL also generates events when only modifiers are pressed, we cannot // SDL also generates events when only modifiers are pressed, we cannot
// ignore them totally, for example press 'a' first then press 'Control', // ignore them totally, for example press 'a' first then press 'Control',
// if we ignore 'Control' event, only 'a' is sent. // if we ignore 'Control' event, only 'a' is sent.
if (scancode >= SC_HID_KEYBOARD_KEYS && !scancode_is_modifier(scancode)) { if (scancode < HID_KEYBOARD_KEYS || scancode_is_modifier(scancode)) {
// Scancode to ignore return convert_hid_keyboard_event(kb, hid_event, event);
}
return false; return false;
} }
if (!sc_hid_keyboard_event_init(hid_event)) {
LOGW("Could not initialize HID keyboard event");
return false;
}
unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->keysym.mod);
if (scancode < SC_HID_KEYBOARD_KEYS) {
// Pressed is true and released is false
kb->keys[scancode] = (event->type == SDL_KEYDOWN);
LOGV("keys[%02x] = %s", scancode,
kb->keys[scancode] ? "true" : "false");
}
hid_event->buffer[HID_KEYBOARD_INDEX_MODIFIER] = modifiers;
unsigned char *keys_buffer = &hid_event->buffer[HID_KEYBOARD_INDEX_KEYS];
// Re-calculate pressed keys every time
int keys_pressed_count = 0;
for (int i = 0; i < SC_HID_KEYBOARD_KEYS; ++i) {
if (kb->keys[i]) {
// USB HID protocol says that if keys exceeds report count, a
// phantom state should be reported
if (keys_pressed_count >= HID_KEYBOARD_MAX_KEYS) {
// Pantom state:
// - Modifiers
// - Reserved
// - ErrorRollOver * HID_MAX_KEYS
memset(keys_buffer, HID_ERROR_ROLL_OVER, HID_KEYBOARD_MAX_KEYS);
goto end;
}
keys_buffer[keys_pressed_count] = i;
++keys_pressed_count;
}
}
end:
LOGV("hid keyboard: key %-4s scancode=%02x (%u) mod=%02x",
event->type == SDL_KEYDOWN ? "down" : "up", event->keysym.scancode,
event->keysym.scancode, modifiers);
return true;
}
static bool
push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) {
bool capslock = sdl_mod & KMOD_CAPS;
bool numlock = sdl_mod & KMOD_NUM;
if (!capslock && !numlock) {
// Nothing to do
return true;
}
struct sc_hid_event hid_event;
if (!sc_hid_keyboard_event_init(&hid_event)) {
LOGW("Could not initialize HID keyboard event");
return false;
}
#define SC_SCANCODE_CAPSLOCK SDL_SCANCODE_CAPSLOCK
#define SC_SCANCODE_NUMLOCK SDL_SCANCODE_NUMLOCKCLEAR
unsigned i = 0;
if (capslock) {
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
++i;
}
if (numlock) {
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
++i;
}
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event");
return false;
}
LOGD("HID keyboard state synchronized");
return true;
}
static void static void
sc_key_processor_process_key(struct sc_key_processor *kp, sc_key_processor_process_key(struct sc_key_processor *kp,
const SDL_KeyboardEvent *event) { const SDL_KeyboardEvent *event) {
@ -285,33 +295,12 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
return; return;
} }
struct sc_hid_keyboard *kb = DOWNCAST(kp); struct hid_keyboard *kb = DOWNCAST(kp);
struct sc_hid_event hid_event; struct hid_event hid_event;
// Not all keys are supported, just ignore unsupported keys // Not all keys are supported, just ignore unsupported keys
if (convert_hid_keyboard_event(kb, &hid_event, event)) { if (hid_keyboard_convert_event(kb, &hid_event, event)) {
if (!kb->mod_lock_synchronized) { if (!aoa_push_hid_event(kb->aoa, &hid_event)) {
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
// keyboard state
if (push_mod_lock_state(kb, event->keysym.mod)) {
kb->mod_lock_synchronized = true;
}
}
SDL_Keycode keycode = event->keysym.sym;
bool down = event->type == SDL_KEYDOWN;
bool ctrl = event->keysym.mod & KMOD_CTRL;
bool shift = event->keysym.mod & KMOD_SHIFT;
if (ctrl && !shift && keycode == SDLK_v && down) {
// Ctrl+v is pressed, so clipboard synchronization has been
// requested. Wait a bit so that the clipboard is set before
// injecting Ctrl+v via HID, otherwise it would paste the old
// clipboard content.
hid_event.delay = SC_TICK_FROM_MS(5);
}
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event"); LOGW("Could request HID event");
} }
} }
@ -327,21 +316,21 @@ sc_key_processor_process_text(struct sc_key_processor *kp,
} }
bool bool
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) { hid_keyboard_init(struct hid_keyboard *kb, struct aoa *aoa, unsigned lock_mod) {
kb->aoa = aoa; kb->aoa = aoa;
bool ok = sc_aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID, // FIXME In practice, sending CAPS_LOCK immediately after fails with a Pipe
// error but we must know immediately if this fails or not
bool ok = aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID,
keyboard_report_desc, keyboard_report_desc,
ARRAY_LEN(keyboard_report_desc)); ARRAY_LEN(keyboard_report_desc));
if (!ok) { if (!ok) {
LOGW("Register HID keyboard failed"); LOGW("Register HID for keyboard failed");
return false; return false;
} }
// Reset all states // Reset all states
memset(kb->keys, false, SC_HID_KEYBOARD_KEYS); memset(kb->keys, false, HID_KEYBOARD_KEYS);
kb->mod_lock_synchronized = false;
static const struct sc_key_processor_ops ops = { static const struct sc_key_processor_ops ops = {
.process_key = sc_key_processor_process_key, .process_key = sc_key_processor_process_key,
@ -350,14 +339,18 @@ sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
kb->key_processor.ops = &ops; kb->key_processor.ops = &ops;
// FIXME to avoid pipe error
usleep(100000);
send_mod_lock_state(kb, lock_mod);
return true; return true;
} }
void void
sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb) { hid_keyboard_destroy(struct hid_keyboard *kb) {
// Unregister HID keyboard so the soft keyboard shows again on Android // Unregister HID keyboard so the soft keyboard shows again on Android
bool ok = sc_aoa_unregister_hid(kb->aoa, HID_KEYBOARD_ACCESSORY_ID); bool ok = aoa_unregister_hid(kb->aoa, HID_KEYBOARD_ACCESSORY_ID);
if (!ok) { if (!ok) {
LOGW("Could not unregister HID keyboard"); LOGW("Could not unregister HID");
} }
} }

View File

@ -1,10 +1,12 @@
#ifndef SC_HID_KEYBOARD_H #ifndef HID_KEYBOARD_H
#define SC_HID_KEYBOARD_H #define HID_KEYBOARD_H
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL.h>
#include "aoa_hid.h" #include "aoa_hid.h"
#include "trait/key_processor.h" #include "trait/key_processor.h"
@ -12,7 +14,11 @@
// Maybe SDL_Keycode is used by most people, but SDL_Scancode is taken from USB // Maybe SDL_Keycode is used by most people, but SDL_Scancode is taken from USB
// HID protocol. // HID protocol.
// 0x65 is Application, typically AT-101 Keyboard ends here. // 0x65 is Application, typically AT-101 Keyboard ends here.
#define SC_HID_KEYBOARD_KEYS 0x66 #define HID_KEYBOARD_KEYS 0x66
#define SC_MOD_MASK 0x3
#define SC_MOD_CAPSLOCK 0x1
#define SC_MOD_NUMLOCK 0x2
/** /**
* HID keyboard events are sequence-based, every time keyboard state changes * HID keyboard events are sequence-based, every time keyboard state changes
@ -26,19 +32,17 @@
* phantom state. Don't forget that modifiers should be updated too, even for * phantom state. Don't forget that modifiers should be updated too, even for
* phantom state. * phantom state.
*/ */
struct sc_hid_keyboard { struct hid_keyboard {
struct sc_key_processor key_processor; // key processor trait struct sc_key_processor key_processor; // key processor trait
struct sc_aoa *aoa; struct aoa *aoa;
bool keys[SC_HID_KEYBOARD_KEYS]; bool keys[HID_KEYBOARD_KEYS];
bool mod_lock_synchronized;
}; };
bool bool
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa); hid_keyboard_init(struct hid_keyboard *kb, struct aoa *aoa, unsigned lock_mod);
void void
sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb); hid_keyboard_destroy(struct hid_keyboard *kb);
#endif #endif

View File

@ -1,290 +0,0 @@
#include "icon.h"
#include <assert.h>
#include <stdbool.h>
#include <libavformat/avformat.h>
#include <libavutil/pixdesc.h>
#include <libavutil/pixfmt.h>
#include "config.h"
#include "compat.h"
#include "util/file.h"
#include "util/log.h"
#include "util/str.h"
#define SCRCPY_PORTABLE_ICON_FILENAME "icon.png"
#define SCRCPY_DEFAULT_ICON_PATH \
PREFIX "/share/icons/hicolor/256x256/apps/scrcpy.png"
static char *
get_icon_path(void) {
#ifdef __WINDOWS__
const wchar_t *icon_path_env = _wgetenv(L"SCRCPY_ICON_PATH");
#else
const char *icon_path_env = getenv("SCRCPY_ICON_PATH");
#endif
if (icon_path_env) {
// if the envvar is set, use it
#ifdef __WINDOWS__
char *icon_path = sc_str_from_wchars(icon_path_env);
#else
char *icon_path = strdup(icon_path_env);
#endif
if (!icon_path) {
LOGE("Could not allocate memory");
return NULL;
}
LOGD("Using SCRCPY_ICON_PATH: %s", icon_path);
return icon_path;
}
#ifndef PORTABLE
LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH);
char *icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH);
if (!icon_path) {
LOGE("Could not allocate memory");
return NULL;
}
#else
char *icon_path = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME);
if (!icon_path) {
LOGE("Could not get icon path");
return NULL;
}
LOGD("Using icon (portable): %s", icon_path);
#endif
return icon_path;
}
static AVFrame *
decode_image(const char *path) {
AVFrame *result = NULL;
AVFormatContext *ctx = avformat_alloc_context();
if (!ctx) {
LOGE("Could not allocate image decoder context");
return NULL;
}
if (avformat_open_input(&ctx, path, NULL, NULL) < 0) {
LOGE("Could not open image codec: %s", path);
goto free_ctx;
}
if (avformat_find_stream_info(ctx, NULL) < 0) {
LOGE("Could not find image stream info");
goto close_input;
}
int stream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (stream < 0 ) {
LOGE("Could not find best image stream");
goto close_input;
}
AVCodecParameters *params = ctx->streams[stream]->codecpar;
AVCodec *codec = avcodec_find_decoder(params->codec_id);
if (!codec) {
LOGE("Could not find image decoder");
goto close_input;
}
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
LOGE("Could not allocate codec context");
goto close_input;
}
if (avcodec_parameters_to_context(codec_ctx, params) < 0) {
LOGE("Could not fill codec context");
goto free_codec_ctx;
}
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
LOGE("Could not open image codec");
goto free_codec_ctx;
}
AVFrame *frame = av_frame_alloc();
if (!frame) {
LOGE("Could not allocate frame");
goto close_codec;
}
AVPacket *packet = av_packet_alloc();
if (!packet) {
LOGE("Could not allocate packet");
av_frame_free(&frame);
goto close_codec;
}
if (av_read_frame(ctx, packet) < 0) {
LOGE("Could not read frame");
av_packet_free(&packet);
av_frame_free(&frame);
goto close_codec;
}
int ret;
if ((ret = avcodec_send_packet(codec_ctx, packet)) < 0) {
LOGE("Could not send icon packet: %d", ret);
av_packet_free(&packet);
av_frame_free(&frame);
goto close_codec;
}
if ((ret = avcodec_receive_frame(codec_ctx, frame)) != 0) {
LOGE("Could not receive icon frame: %d", ret);
av_packet_free(&packet);
av_frame_free(&frame);
goto close_codec;
}
av_packet_free(&packet);
result = frame;
close_codec:
avcodec_close(codec_ctx);
free_codec_ctx:
avcodec_free_context(&codec_ctx);
close_input:
avformat_close_input(&ctx);
free_ctx:
avformat_free_context(ctx);
return result;
}
#if !SDL_VERSION_ATLEAST(2, 0, 10)
// SDL_PixelFormatEnum has been introduced in SDL 2.0.10. Use int for older SDL
// versions.
typedef int SDL_PixelFormatEnum;
#endif
static SDL_PixelFormatEnum
to_sdl_pixel_format(enum AVPixelFormat fmt) {
switch (fmt) {
case AV_PIX_FMT_RGB24: return SDL_PIXELFORMAT_RGB24;
case AV_PIX_FMT_BGR24: return SDL_PIXELFORMAT_BGR24;
case AV_PIX_FMT_ARGB: return SDL_PIXELFORMAT_ARGB32;
case AV_PIX_FMT_RGBA: return SDL_PIXELFORMAT_RGBA32;
case AV_PIX_FMT_ABGR: return SDL_PIXELFORMAT_ABGR32;
case AV_PIX_FMT_BGRA: return SDL_PIXELFORMAT_BGRA32;
case AV_PIX_FMT_RGB565BE: return SDL_PIXELFORMAT_RGB565;
case AV_PIX_FMT_RGB555BE: return SDL_PIXELFORMAT_RGB555;
case AV_PIX_FMT_BGR565BE: return SDL_PIXELFORMAT_BGR565;
case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_BGR555;
case AV_PIX_FMT_RGB444BE: return SDL_PIXELFORMAT_RGB444;
#if SDL_VERSION_ATLEAST(2, 0, 12)
case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_BGR444;
#endif
case AV_PIX_FMT_PAL8: return SDL_PIXELFORMAT_INDEX8;
default: return SDL_PIXELFORMAT_UNKNOWN;
}
}
static SDL_Surface *
load_from_path(const char *path) {
AVFrame *frame = decode_image(path);
if (!frame) {
return NULL;
}
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
if (!desc) {
LOGE("Could not get icon format descriptor");
goto error;
}
bool is_packed = !(desc->flags & AV_PIX_FMT_FLAG_PLANAR);
if (!is_packed) {
LOGE("Could not load non-packed icon");
goto error;
}
SDL_PixelFormatEnum format = to_sdl_pixel_format(frame->format);
if (format == SDL_PIXELFORMAT_UNKNOWN) {
LOGE("Unsupported icon pixel format: %s (%d)", desc->name,
frame->format);
goto error;
}
int bits_per_pixel = av_get_bits_per_pixel(desc);
SDL_Surface *surface =
SDL_CreateRGBSurfaceWithFormatFrom(frame->data[0],
frame->width, frame->height,
bits_per_pixel,
frame->linesize[0],
format);
if (!surface) {
LOGE("Could not create icon surface");
goto error;
}
if (frame->format == AV_PIX_FMT_PAL8) {
// Initialize the SDL palette
uint8_t *data = frame->data[1];
SDL_Color colors[256];
for (int i = 0; i < 256; ++i) {
SDL_Color *color = &colors[i];
// The palette is transported in AVFrame.data[1], is 1024 bytes
// long (256 4-byte entries) and is formatted the same as in
// AV_PIX_FMT_RGB32 described above (i.e., it is also
// endian-specific).
// <https://ffmpeg.org/doxygen/4.1/pixfmt_8h.html#a9a8e335cf3be472042bc9f0cf80cd4c5>
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
color->a = data[i * 4];
color->r = data[i * 4 + 1];
color->g = data[i * 4 + 2];
color->b = data[i * 4 + 3];
#else
color->a = data[i * 4 + 3];
color->r = data[i * 4 + 2];
color->g = data[i * 4 + 1];
color->b = data[i * 4];
#endif
}
SDL_Palette *palette = surface->format->palette;
assert(palette);
int ret = SDL_SetPaletteColors(palette, colors, 0, 256);
if (ret) {
LOGE("Could not set palette colors");
SDL_FreeSurface(surface);
goto error;
}
}
surface->userdata = frame; // frame owns the data
return surface;
error:
av_frame_free(&frame);
return NULL;
}
SDL_Surface *
scrcpy_icon_load() {
char *icon_path = get_icon_path();
if (!icon_path) {
return NULL;
}
SDL_Surface *icon = load_from_path(icon_path);
free(icon_path);
return icon;
}
void
scrcpy_icon_destroy(SDL_Surface *icon) {
AVFrame *frame = icon->userdata;
assert(frame);
av_frame_free(&frame);
SDL_FreeSurface(icon);
}

View File

@ -1,16 +0,0 @@
#ifndef ICON_H
#define ICON_H
#include "common.h"
#include <stdbool.h>
#include <SDL2/SDL.h>
#include <libavformat/avformat.h>
SDL_Surface *
scrcpy_icon_load(void);
void
scrcpy_icon_destroy(SDL_Surface *icon);
#endif

53
app/src/icon.xpm Normal file
View File

@ -0,0 +1,53 @@
/* XPM */
static char * icon_xpm[] = {
"48 48 2 1",
" c None",
". c #96C13E",
" .. .. ",
" ... ... ",
" ... ...... ... ",
" ................ ",
" .............. ",
" ................ ",
" .................. ",
" .................... ",
" ..... ........ ..... ",
" ..... ........ ..... ",
" ...................... ",
" ........................ ",
" ........................ ",
" ........................ ",
" ",
" ",
" .... ........................ .... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" .... ........................ .... ",
" ........................ ",
" ...................... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" .... .... "};

View File

@ -332,7 +332,7 @@ input_manager_process_text_input(struct input_manager *im,
static bool static bool
simulate_virtual_finger(struct input_manager *im, simulate_virtual_finger(struct input_manager *im,
enum android_motionevent_action action, enum android_motionevent_action action,
struct sc_point point) { struct point point) {
bool up = action == AMOTION_EVENT_ACTION_UP; bool up = action == AMOTION_EVENT_ACTION_UP;
struct control_msg msg; struct control_msg msg;
@ -352,8 +352,8 @@ simulate_virtual_finger(struct input_manager *im,
return true; return true;
} }
static struct sc_point static struct point
inverse_point(struct sc_point point, struct sc_size size) { inverse_point(struct point point, struct size size) {
point.x = size.width - point.x; point.x = size.width - point.x;
point.y = size.height - point.y; point.y = size.height - point.y;
return point; return point;
@ -376,6 +376,8 @@ input_manager_process_key(struct input_manager *im,
bool smod = is_shortcut_mod(im, mod); bool smod = is_shortcut_mod(im, mod);
LOGD("=== %x", (int) mod);
if (down && !repeat) { if (down && !repeat) {
if (keycode == im->last_keycode && mod == im->last_mod) { if (keycode == im->last_keycode && mod == im->last_mod) {
++im->key_repeat; ++im->key_repeat;
@ -545,10 +547,10 @@ input_manager_process_mouse_motion(struct input_manager *im,
im->mp->ops->process_mouse_motion(im->mp, event); im->mp->ops->process_mouse_motion(im->mp, event);
if (im->vfinger_down) { if (im->vfinger_down) {
struct sc_point mouse = struct point mouse =
screen_convert_window_to_frame_coords(im->screen, event->x, screen_convert_window_to_frame_coords(im->screen, event->x,
event->y); event->y);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size); struct point vfinger = inverse_point(mouse, im->screen->frame_size);
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger); simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
} }
} }
@ -630,10 +632,10 @@ input_manager_process_mouse_button(struct input_manager *im,
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL)) #define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
if ((down && !im->vfinger_down && CTRL_PRESSED) if ((down && !im->vfinger_down && CTRL_PRESSED)
|| (!down && im->vfinger_down)) { || (!down && im->vfinger_down)) {
struct sc_point mouse = struct point mouse =
screen_convert_window_to_frame_coords(im->screen, event->x, screen_convert_window_to_frame_coords(im->screen, event->x,
event->y); event->y);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size); struct point vfinger = inverse_point(mouse, im->screen->frame_size);
enum android_motionevent_action action = down enum android_motionevent_action action = down
? AMOTION_EVENT_ACTION_DOWN ? AMOTION_EVENT_ACTION_DOWN
: AMOTION_EVENT_ACTION_UP; : AMOTION_EVENT_ACTION_UP;

View File

@ -9,7 +9,7 @@
#include "controller.h" #include "controller.h"
#include "fps_counter.h" #include "fps_counter.h"
#include "options.h" #include "scrcpy.h"
#include "screen.h" #include "screen.h"
#include "trait/key_processor.h" #include "trait/key_processor.h"
#include "trait/mouse_processor.h" #include "trait/mouse_processor.h"

View File

@ -6,7 +6,7 @@
#include <stdbool.h> #include <stdbool.h>
#include "controller.h" #include "controller.h"
#include "options.h" #include "scrcpy.h"
#include "trait/key_processor.h" #include "trait/key_processor.h"
struct sc_keyboard_inject { struct sc_keyboard_inject {

View File

@ -1,3 +1,5 @@
#include "scrcpy.h"
#include "common.h" #include "common.h"
#include <assert.h> #include <assert.h>
@ -11,8 +13,6 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "cli.h" #include "cli.h"
#include "options.h"
#include "scrcpy.h"
#include "util/log.h" #include "util/log.h"
static void static void
@ -47,11 +47,8 @@ main(int argc, char *argv[]) {
setbuf(stderr, NULL); setbuf(stderr, NULL);
#endif #endif
printf("scrcpy " SCRCPY_VERSION
" <https://github.com/Genymobile/scrcpy>\n");
struct scrcpy_cli_args args = { struct scrcpy_cli_args args = {
.opts = scrcpy_options_default, .opts = SCRCPY_OPTIONS_DEFAULT,
.help = false, .help = false,
.version = false, .version = false,
}; };
@ -76,6 +73,8 @@ main(int argc, char *argv[]) {
return 0; return 0;
} }
LOGI("scrcpy " SCRCPY_VERSION " <https://github.com/Genymobile/scrcpy>");
#ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL #ifdef SCRCPY_LAVF_REQUIRES_REGISTER_ALL
av_register_all(); av_register_all();
#endif #endif

View File

@ -125,7 +125,7 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
int mouse_y; int mouse_y;
SDL_GetMouseState(&mouse_x, &mouse_y); SDL_GetMouseState(&mouse_x, &mouse_y);
struct sc_position position = { struct position position = {
.screen_size = screen->frame_size, .screen_size = screen->frame_size,
.point = screen_convert_window_to_frame_coords(screen, .point = screen_convert_window_to_frame_coords(screen,
mouse_x, mouse_y), mouse_x, mouse_y),

View File

@ -6,6 +6,7 @@
#include <stdbool.h> #include <stdbool.h>
#include "controller.h" #include "controller.h"
#include "scrcpy.h"
#include "screen.h" #include "screen.h"
#include "trait/mouse_processor.h" #include "trait/mouse_processor.h"

View File

@ -1,54 +0,0 @@
#include "options.h"
const struct scrcpy_options scrcpy_options_default = {
.serial = NULL,
.crop = NULL,
.record_filename = NULL,
.window_title = NULL,
.push_target = NULL,
.render_driver = NULL,
.codec_options = NULL,
.encoder_name = NULL,
#ifdef HAVE_V4L2
.v4l2_device = NULL,
#endif
.log_level = SC_LOG_LEVEL_INFO,
.record_format = SC_RECORD_FORMAT_AUTO,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT,
.port_range = {
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
.last = DEFAULT_LOCAL_PORT_RANGE_LAST,
},
.shortcut_mods = {
.data = {SC_MOD_LALT, SC_MOD_LSUPER},
.count = 2,
},
.max_size = 0,
.bit_rate = DEFAULT_BIT_RATE,
.max_fps = 0,
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
.rotation = 0,
.window_x = SC_WINDOW_POSITION_UNDEFINED,
.window_y = SC_WINDOW_POSITION_UNDEFINED,
.window_width = 0,
.window_height = 0,
.display_id = 0,
.display_buffer = 0,
.v4l2_buffer = 0,
.show_touches = false,
.fullscreen = false,
.always_on_top = false,
.control = true,
.display = true,
.turn_screen_off = false,
.prefer_text = false,
.window_borderless = false,
.mipmaps = true,
.stay_awake = false,
.force_adb_forward = false,
.disable_screensaver = false,
.forward_key_repeat = true,
.forward_all_clicks = false,
.legacy_paste = false,
.power_off_on_close = false,
};

View File

@ -1,113 +0,0 @@
#ifndef SCRCPY_OPTIONS_H
#define SCRCPY_OPTIONS_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "util/tick.h"
enum sc_log_level {
SC_LOG_LEVEL_VERBOSE,
SC_LOG_LEVEL_DEBUG,
SC_LOG_LEVEL_INFO,
SC_LOG_LEVEL_WARN,
SC_LOG_LEVEL_ERROR,
};
enum sc_record_format {
SC_RECORD_FORMAT_AUTO,
SC_RECORD_FORMAT_MP4,
SC_RECORD_FORMAT_MKV,
};
enum sc_lock_video_orientation {
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
// lock the current orientation when scrcpy starts
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
SC_LOCK_VIDEO_ORIENTATION_1,
SC_LOCK_VIDEO_ORIENTATION_2,
SC_LOCK_VIDEO_ORIENTATION_3,
};
enum sc_keyboard_input_mode {
SC_KEYBOARD_INPUT_MODE_INJECT,
SC_KEYBOARD_INPUT_MODE_HID,
};
#define SC_MAX_SHORTCUT_MODS 8
enum sc_shortcut_mod {
SC_MOD_LCTRL = 1 << 0,
SC_MOD_RCTRL = 1 << 1,
SC_MOD_LALT = 1 << 2,
SC_MOD_RALT = 1 << 3,
SC_MOD_LSUPER = 1 << 4,
SC_MOD_RSUPER = 1 << 5,
};
struct sc_shortcut_mods {
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
};
struct sc_port_range {
uint16_t first;
uint16_t last;
};
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
struct scrcpy_options {
const char *serial;
const char *crop;
const char *record_filename;
const char *window_title;
const char *push_target;
const char *render_driver;
const char *codec_options;
const char *encoder_name;
#ifdef HAVE_V4L2
const char *v4l2_device;
#endif
enum sc_log_level log_level;
enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode;
struct sc_port_range port_range;
struct sc_shortcut_mods shortcut_mods;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
enum sc_lock_video_orientation lock_video_orientation;
uint8_t rotation;
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width;
uint16_t window_height;
uint32_t display_id;
sc_tick display_buffer;
sc_tick v4l2_buffer;
bool show_touches;
bool fullscreen;
bool always_on_top;
bool control;
bool display;
bool turn_screen_off;
bool prefer_text;
bool window_borderless;
bool mipmaps;
bool stay_awake;
bool force_adb_forward;
bool disable_screensaver;
bool forward_key_repeat;
bool forward_all_clicks;
bool legacy_paste;
bool power_off_on_close;
};
extern const struct scrcpy_options scrcpy_options_default;
#endif

View File

@ -7,7 +7,7 @@
#include "util/log.h" #include "util/log.h"
bool bool
receiver_init(struct receiver *receiver, sc_socket control_socket) { receiver_init(struct receiver *receiver, socket_t control_socket) {
bool ok = sc_mutex_init(&receiver->mutex); bool ok = sc_mutex_init(&receiver->mutex);
if (!ok) { if (!ok) {
return false; return false;

View File

@ -11,13 +11,13 @@
// receive events from the device // receive events from the device
// managed by the controller // managed by the controller
struct receiver { struct receiver {
sc_socket control_socket; socket_t control_socket;
sc_thread thread; sc_thread thread;
sc_mutex mutex; sc_mutex mutex;
}; };
bool bool
receiver_init(struct receiver *receiver, sc_socket control_socket); receiver_init(struct receiver *receiver, socket_t control_socket);
void void
receiver_destroy(struct receiver *receiver); receiver_destroy(struct receiver *receiver);

View File

@ -6,7 +6,7 @@
#include <libavutil/time.h> #include <libavutil/time.h>
#include "util/log.h" #include "util/log.h"
#include "util/str.h" #include "util/str_util.h"
/** Downcast packet_sink to recorder */ /** Downcast packet_sink to recorder */
#define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink) #define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink)
@ -26,7 +26,7 @@ find_muxer(const char *name) {
oformat = av_oformat_next(oformat); oformat = av_oformat_next(oformat);
#endif #endif
// until null or containing the requested name // until null or containing the requested name
} while (oformat && !sc_str_list_contains(oformat->name, ',', name)); } while (oformat && !strlist_contains(oformat->name, ',', name));
return oformat; return oformat;
} }
@ -372,7 +372,7 @@ bool
recorder_init(struct recorder *recorder, recorder_init(struct recorder *recorder,
const char *filename, const char *filename,
enum sc_record_format format, enum sc_record_format format,
struct sc_size declared_frame_size) { struct size declared_frame_size) {
recorder->filename = strdup(filename); recorder->filename = strdup(filename);
if (!recorder->filename) { if (!recorder->filename) {
LOGE("Could not strdup filename"); LOGE("Could not strdup filename");

View File

@ -7,7 +7,7 @@
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "coords.h" #include "coords.h"
#include "options.h" #include "scrcpy.h"
#include "trait/packet_sink.h" #include "trait/packet_sink.h"
#include "util/queue.h" #include "util/queue.h"
#include "util/thread.h" #include "util/thread.h"
@ -25,7 +25,7 @@ struct recorder {
char *filename; char *filename;
enum sc_record_format format; enum sc_record_format format;
AVFormatContext *ctx; AVFormatContext *ctx;
struct sc_size declared_frame_size; struct size declared_frame_size;
bool header_written; bool header_written;
sc_thread thread; sc_thread thread;
@ -44,7 +44,7 @@ struct recorder {
bool bool
recorder_init(struct recorder *recorder, const char *filename, recorder_init(struct recorder *recorder, const char *filename,
enum sc_record_format format, struct sc_size declared_frame_size); enum sc_record_format format, struct size declared_frame_size);
void void
recorder_destroy(struct recorder *recorder); recorder_destroy(struct recorder *recorder);

View File

@ -27,6 +27,7 @@
#include "screen.h" #include "screen.h"
#include "server.h" #include "server.h"
#include "stream.h" #include "stream.h"
#include "tiny_xpm.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
@ -34,7 +35,7 @@
#endif #endif
struct scrcpy { struct scrcpy {
struct sc_server server; struct server server;
struct screen screen; struct screen screen;
struct stream stream; struct stream stream;
struct decoder decoder; struct decoder decoder;
@ -45,42 +46,53 @@ struct scrcpy {
struct controller controller; struct controller controller;
struct file_handler file_handler; struct file_handler file_handler;
#ifdef HAVE_AOA_HID #ifdef HAVE_AOA_HID
struct sc_aoa aoa; struct aoa aoa;
#endif #endif
union { union {
struct sc_keyboard_inject keyboard_inject; struct sc_keyboard_inject keyboard_inject;
#ifdef HAVE_AOA_HID #ifdef HAVE_AOA_HID
struct sc_hid_keyboard keyboard_hid; struct hid_keyboard keyboard_hid;
#endif #endif
}; };
struct sc_mouse_inject mouse_inject; struct sc_mouse_inject mouse_inject;
struct input_manager input_manager; struct input_manager input_manager;
}; };
static inline void
push_event(uint32_t type, const char *name) {
SDL_Event event;
event.type = type;
int ret = SDL_PushEvent(&event);
if (ret < 0) {
LOGE("Could not post %s event: %s", name, SDL_GetError());
// What could we do?
}
}
#define PUSH_EVENT(TYPE) push_event(TYPE, # TYPE)
#ifdef _WIN32 #ifdef _WIN32
BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) { BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
if (ctrl_type == CTRL_C_EVENT) { if (ctrl_type == CTRL_C_EVENT) {
PUSH_EVENT(SDL_QUIT); SDL_Event event;
event.type = SDL_QUIT;
SDL_PushEvent(&event);
return TRUE; return TRUE;
} }
return FALSE; return FALSE;
} }
#endif // _WIN32 #endif // _WIN32
static void // init SDL and set appropriate hints
sdl_set_hints(const char *render_driver) { static bool
sdl_init_and_configure(bool display, const char *render_driver,
bool disable_screensaver) {
uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS;
if (SDL_Init(flags)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
return false;
}
atexit(SDL_Quit);
#ifdef _WIN32
// Clean up properly on Ctrl+C on Windows
bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE);
if (!ok) {
LOGW("Could not set Ctrl+C handler");
}
#endif // _WIN32
if (!display) {
return true;
}
if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) { if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) {
LOGW("Could not set render driver"); LOGW("Could not set render driver");
@ -98,15 +110,6 @@ sdl_set_hints(const char *render_driver) {
} }
#endif #endif
#ifdef SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
// Disable synthetic mouse events from touch events
// Touch events with id SDL_TOUCH_MOUSEID are ignored anyway, but it is
// better not to generate them in the first place.
if (!SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0")) {
LOGW("Could not disable synthetic mouse events");
}
#endif
#ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR #ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
// Disable compositor bypassing on X11 // Disable compositor bypassing on X11
if (!SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0")) { if (!SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0")) {
@ -118,21 +121,6 @@ sdl_set_hints(const char *render_driver) {
if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) { if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
LOGW("Could not disable minimize on focus loss"); LOGW("Could not disable minimize on focus loss");
} }
}
static void
sdl_configure(bool display, bool disable_screensaver) {
#ifdef _WIN32
// Clean up properly on Ctrl+C on Windows
bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE);
if (!ok) {
LOGW("Could not set Ctrl+C handler");
}
#endif // _WIN32
if (!display) {
return;
}
if (disable_screensaver) { if (disable_screensaver) {
LOGD("Screensaver disabled"); LOGD("Screensaver disabled");
@ -141,6 +129,8 @@ sdl_configure(bool display, bool disable_screensaver) {
LOGD("Screensaver enabled"); LOGD("Screensaver enabled");
SDL_EnableScreenSaver(); SDL_EnableScreenSaver();
} }
return true;
} }
static bool static bool
@ -217,29 +207,6 @@ event_loop(struct scrcpy *s, const struct scrcpy_options *options) {
return false; return false;
} }
static bool
await_for_server(void) {
SDL_Event event;
while (SDL_WaitEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
LOGD("User requested to quit");
return false;
case EVENT_SERVER_CONNECTION_FAILED:
LOGE("Server connection failed");
return false;
case EVENT_SERVER_CONNECTED:
LOGD("Server connected");
return true;
default:
break;
}
}
LOGE("SDL_WaitEvent() error: %s", SDL_GetError());
return false;
}
static SDL_LogPriority static SDL_LogPriority
sdl_priority_from_av_level(int level) { sdl_priority_from_av_level(int level) {
switch (level) { switch (level) {
@ -282,33 +249,21 @@ stream_on_eos(struct stream *stream, void *userdata) {
(void) stream; (void) stream;
(void) userdata; (void) userdata;
PUSH_EVENT(EVENT_STREAM_STOPPED); SDL_Event stop_event;
stop_event.type = EVENT_STREAM_STOPPED;
SDL_PushEvent(&stop_event);
} }
static void static unsigned
sc_server_on_connection_failed(struct sc_server *server, void *userdata) { to_sc_mod(SDL_Keymod sdl_mod) {
(void) server; unsigned mod = 0;
(void) userdata; if (sdl_mod & KMOD_CAPS) {
mod |= SC_MOD_CAPSLOCK;
PUSH_EVENT(EVENT_SERVER_CONNECTION_FAILED);
} }
if (sdl_mod & KMOD_NUM) {
static void mod |= SC_MOD_NUMLOCK;
sc_server_on_connected(struct sc_server *server, void *userdata) {
(void) server;
(void) userdata;
PUSH_EVENT(EVENT_SERVER_CONNECTED);
} }
return mod;
static void
sc_server_on_disconnected(struct sc_server *server, void *userdata) {
(void) server;
(void) userdata;
LOGD("Server disconnected");
// Do nothing, the disconnection will be handled by the "stream stopped"
// event
} }
bool bool
@ -316,14 +271,10 @@ scrcpy(struct scrcpy_options *options) {
static struct scrcpy scrcpy; static struct scrcpy scrcpy;
struct scrcpy *s = &scrcpy; struct scrcpy *s = &scrcpy;
// Minimal SDL initialization if (!server_init(&s->server)) {
if (SDL_Init(SDL_INIT_EVENTS)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
return false; return false;
} }
atexit(SDL_Quit);
bool ret = false; bool ret = false;
bool server_started = false; bool server_started = false;
@ -340,7 +291,8 @@ scrcpy(struct scrcpy_options *options) {
bool controller_started = false; bool controller_started = false;
bool screen_initialized = false; bool screen_initialized = false;
struct sc_server_params params = { bool record = !!options->record_filename;
struct server_params params = {
.serial = options->serial, .serial = options->serial,
.log_level = options->log_level, .log_level = options->log_level,
.crop = options->crop, .crop = options->crop,
@ -358,47 +310,26 @@ scrcpy(struct scrcpy_options *options) {
.force_adb_forward = options->force_adb_forward, .force_adb_forward = options->force_adb_forward,
.power_off_on_close = options->power_off_on_close, .power_off_on_close = options->power_off_on_close,
}; };
if (!server_start(&s->server, &params)) {
static const struct sc_server_callbacks cbs = {
.on_connection_failed = sc_server_on_connection_failed,
.on_connected = sc_server_on_connected,
.on_disconnected = sc_server_on_disconnected,
};
if (!sc_server_init(&s->server, &params, &cbs, NULL)) {
return false;
}
if (!sc_server_start(&s->server)) {
goto end; goto end;
} }
server_started = true; server_started = true;
if (options->display) { if (!sdl_init_and_configure(options->display, options->render_driver,
sdl_set_hints(options->render_driver); options->disable_screensaver)) {
}
// Initialize SDL video in addition if display is enabled
if (options->display && SDL_Init(SDL_INIT_VIDEO)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
goto end; goto end;
} }
sdl_configure(options->display, options->disable_screensaver); char device_name[DEVICE_NAME_FIELD_LENGTH];
struct size frame_size;
// Await for server without blocking Ctrl+C handling if (!server_connect_to(&s->server, device_name, &frame_size)) {
if (!await_for_server()) {
goto end; goto end;
} }
// It is necessarily initialized here, since the device is connected
struct sc_server_info *info = &s->server.info;
const char *serial = s->server.params.serial;
assert(serial);
if (options->display && options->control) { if (options->display && options->control) {
if (!file_handler_init(&s->file_handler, serial, if (!file_handler_init(&s->file_handler, s->server.serial,
options->push_target)) { options->push_target)) {
goto end; goto end;
} }
@ -416,11 +347,11 @@ scrcpy(struct scrcpy_options *options) {
} }
struct recorder *rec = NULL; struct recorder *rec = NULL;
if (options->record_filename) { if (record) {
if (!recorder_init(&s->recorder, if (!recorder_init(&s->recorder,
options->record_filename, options->record_filename,
options->record_format, options->record_format,
info->frame_size)) { frame_size)) {
goto end; goto end;
} }
rec = &s->recorder; rec = &s->recorder;
@ -466,11 +397,11 @@ scrcpy(struct scrcpy_options *options) {
if (options->display) { if (options->display) {
const char *window_title = const char *window_title =
options->window_title ? options->window_title : info->device_name; options->window_title ? options->window_title : device_name;
struct screen_params screen_params = { struct screen_params screen_params = {
.window_title = window_title, .window_title = window_title,
.frame_size = info->frame_size, .frame_size = frame_size,
.always_on_top = options->always_on_top, .always_on_top = options->always_on_top,
.window_x = options->window_x, .window_x = options->window_x,
.window_y = options->window_y, .window_y = options->window_y,
@ -493,8 +424,8 @@ scrcpy(struct scrcpy_options *options) {
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
if (options->v4l2_device) { if (options->v4l2_device) {
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, frame_size,
info->frame_size, options->v4l2_buffer)) { options->v4l2_buffer)) {
goto end; goto end;
} }
@ -519,19 +450,36 @@ scrcpy(struct scrcpy_options *options) {
#ifdef HAVE_AOA_HID #ifdef HAVE_AOA_HID
bool aoa_hid_ok = false; bool aoa_hid_ok = false;
bool ok = sc_aoa_init(&s->aoa, serial); char *serialno = NULL;
const char *serial = options->serial;
if (!serial) {
serialno = adb_get_serialno();
if (!serialno) {
LOGE("Could not get device serial");
goto aoa_hid_end;
}
serial = serialno;
LOGI("Device serial: %s", serial);
}
bool ok = aoa_init(&s->aoa, serial);
free(serialno);
if (!ok) { if (!ok) {
goto aoa_hid_end; goto aoa_hid_end;
} }
if (!sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) { if (!aoa_start(&s->aoa)) {
sc_aoa_destroy(&s->aoa); aoa_destroy(&s->aoa);
goto aoa_hid_end; goto aoa_hid_end;
} }
if (!sc_aoa_start(&s->aoa)) { // FIXME: SDL_GetModState() always returns 0 here :/
sc_hid_keyboard_destroy(&s->keyboard_hid); unsigned mod = to_sc_mod(SDL_GetModState());
sc_aoa_destroy(&s->aoa); if (!hid_keyboard_init(&s->keyboard_hid, &s->aoa, mod)) {
aoa_join(&s->aoa);
aoa_stop(&s->aoa);
aoa_destroy(&s->aoa);
goto aoa_hid_end; goto aoa_hid_end;
} }
@ -544,13 +492,13 @@ aoa_hid_end:
if (!aoa_hid_ok) { if (!aoa_hid_ok) {
LOGE("Failed to enable HID over AOA, " LOGE("Failed to enable HID over AOA, "
"fallback to default keyboard injection method " "fallback to default keyboard injection method "
"(-K/--hid-keyboard ignored)"); "(-K/--keyboard-hid ignored)");
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT; options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
} }
#else #else
LOGE("HID over AOA is not supported on this platform, " LOGE("HID over AOA is not supported on this platform, "
"fallback to default keyboard injection method " "fallback to default keyboard injection method "
"(-K/--hid-keyboard ignored)"); "(-K/--keyboard-hid ignored)");
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT; options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
#endif #endif
} }
@ -581,8 +529,8 @@ end:
// end-of-stream // end-of-stream
#ifdef HAVE_AOA_HID #ifdef HAVE_AOA_HID
if (aoa_hid_initialized) { if (aoa_hid_initialized) {
sc_hid_keyboard_destroy(&s->keyboard_hid); hid_keyboard_destroy(&s->keyboard_hid);
sc_aoa_stop(&s->aoa); aoa_stop(&s->aoa);
} }
#endif #endif
if (controller_started) { if (controller_started) {
@ -597,7 +545,7 @@ end:
if (server_started) { if (server_started) {
// shutdown the sockets and kill the server // shutdown the sockets and kill the server
sc_server_stop(&s->server); server_stop(&s->server);
} }
// now that the sockets are shutdown, the stream and controller are // now that the sockets are shutdown, the stream and controller are
@ -614,8 +562,8 @@ end:
#ifdef HAVE_AOA_HID #ifdef HAVE_AOA_HID
if (aoa_hid_initialized) { if (aoa_hid_initialized) {
sc_aoa_join(&s->aoa); aoa_join(&s->aoa);
sc_aoa_destroy(&s->aoa); aoa_destroy(&s->aoa);
} }
#endif #endif
@ -642,7 +590,7 @@ end:
file_handler_destroy(&s->file_handler); file_handler_destroy(&s->file_handler);
} }
sc_server_destroy(&s->server); server_destroy(&s->server);
return ret; return ret;
} }

View File

@ -4,7 +4,158 @@
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include "options.h" #include <stddef.h>
#include <stdint.h>
#include "util/tick.h"
enum sc_log_level {
SC_LOG_LEVEL_VERBOSE,
SC_LOG_LEVEL_DEBUG,
SC_LOG_LEVEL_INFO,
SC_LOG_LEVEL_WARN,
SC_LOG_LEVEL_ERROR,
};
enum sc_record_format {
SC_RECORD_FORMAT_AUTO,
SC_RECORD_FORMAT_MP4,
SC_RECORD_FORMAT_MKV,
};
enum sc_lock_video_orientation {
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
// lock the current orientation when scrcpy starts
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
SC_LOCK_VIDEO_ORIENTATION_1,
SC_LOCK_VIDEO_ORIENTATION_2,
SC_LOCK_VIDEO_ORIENTATION_3,
};
enum sc_keyboard_input_mode {
SC_KEYBOARD_INPUT_MODE_INJECT,
SC_KEYBOARD_INPUT_MODE_HID,
};
#define SC_MAX_SHORTCUT_MODS 8
enum sc_shortcut_mod {
SC_MOD_LCTRL = 1 << 0,
SC_MOD_RCTRL = 1 << 1,
SC_MOD_LALT = 1 << 2,
SC_MOD_RALT = 1 << 3,
SC_MOD_LSUPER = 1 << 4,
SC_MOD_RSUPER = 1 << 5,
};
struct sc_shortcut_mods {
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
};
struct sc_port_range {
uint16_t first;
uint16_t last;
};
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
struct scrcpy_options {
const char *serial;
const char *crop;
const char *record_filename;
const char *window_title;
const char *push_target;
const char *render_driver;
const char *codec_options;
const char *encoder_name;
const char *v4l2_device;
enum sc_log_level log_level;
enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode;
struct sc_port_range port_range;
struct sc_shortcut_mods shortcut_mods;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
enum sc_lock_video_orientation lock_video_orientation;
uint8_t rotation;
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width;
uint16_t window_height;
uint32_t display_id;
sc_tick display_buffer;
sc_tick v4l2_buffer;
bool show_touches;
bool fullscreen;
bool always_on_top;
bool control;
bool display;
bool turn_screen_off;
bool prefer_text;
bool window_borderless;
bool mipmaps;
bool stay_awake;
bool force_adb_forward;
bool disable_screensaver;
bool forward_key_repeat;
bool forward_all_clicks;
bool legacy_paste;
bool power_off_on_close;
};
#define SCRCPY_OPTIONS_DEFAULT { \
.serial = NULL, \
.crop = NULL, \
.record_filename = NULL, \
.window_title = NULL, \
.push_target = NULL, \
.render_driver = NULL, \
.codec_options = NULL, \
.encoder_name = NULL, \
.v4l2_device = NULL, \
.log_level = SC_LOG_LEVEL_INFO, \
.record_format = SC_RECORD_FORMAT_AUTO, \
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, \
.port_range = { \
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \
.last = DEFAULT_LOCAL_PORT_RANGE_LAST, \
}, \
.shortcut_mods = { \
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
.count = 2, \
}, \
.max_size = 0, \
.bit_rate = DEFAULT_BIT_RATE, \
.max_fps = 0, \
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED, \
.rotation = 0, \
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
.window_width = 0, \
.window_height = 0, \
.display_id = 0, \
.display_buffer = 0, \
.v4l2_buffer = 0, \
.show_touches = false, \
.fullscreen = false, \
.always_on_top = false, \
.control = true, \
.display = true, \
.turn_screen_off = false, \
.prefer_text = false, \
.window_borderless = false, \
.mipmaps = true, \
.stay_awake = false, \
.force_adb_forward = false, \
.disable_screensaver = false, \
.forward_key_repeat = true, \
.forward_all_clicks = false, \
.legacy_paste = false, \
.power_off_on_close = false, \
}
bool bool
scrcpy(struct scrcpy_options *options); scrcpy(struct scrcpy_options *options);

View File

@ -5,8 +5,9 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "events.h" #include "events.h"
#include "icon.h" #include "icon.xpm"
#include "options.h" #include "scrcpy.h"
#include "tiny_xpm.h"
#include "video_buffer.h" #include "video_buffer.h"
#include "util/log.h" #include "util/log.h"
@ -14,9 +15,9 @@
#define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink) #define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink)
static inline struct sc_size static inline struct size
get_rotated_size(struct sc_size size, int rotation) { get_rotated_size(struct size size, int rotation) {
struct sc_size rotated_size; struct size rotated_size;
if (rotation & 1) { if (rotation & 1) {
rotated_size.width = size.height; rotated_size.width = size.height;
rotated_size.height = size.width; rotated_size.height = size.width;
@ -27,26 +28,26 @@ get_rotated_size(struct sc_size size, int rotation) {
return rotated_size; return rotated_size;
} }
// get the window size in a struct sc_size // get the window size in a struct size
static struct sc_size static struct size
get_window_size(const struct screen *screen) { get_window_size(const struct screen *screen) {
int width; int width;
int height; int height;
SDL_GetWindowSize(screen->window, &width, &height); SDL_GetWindowSize(screen->window, &width, &height);
struct sc_size size; struct size size;
size.width = width; size.width = width;
size.height = height; size.height = height;
return size; return size;
} }
static struct sc_point static struct point
get_window_position(const struct screen *screen) { get_window_position(const struct screen *screen) {
int x; int x;
int y; int y;
SDL_GetWindowPosition(screen->window, &x, &y); SDL_GetWindowPosition(screen->window, &x, &y);
struct sc_point point; struct point point;
point.x = x; point.x = x;
point.y = y; point.y = y;
return point; return point;
@ -54,7 +55,7 @@ get_window_position(const struct screen *screen) {
// set the window size to be applied when fullscreen is disabled // set the window size to be applied when fullscreen is disabled
static void static void
set_window_size(struct screen *screen, struct sc_size new_size) { set_window_size(struct screen *screen, struct size new_size) {
assert(!screen->fullscreen); assert(!screen->fullscreen);
assert(!screen->maximized); assert(!screen->maximized);
SDL_SetWindowSize(screen->window, new_size.width, new_size.height); SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
@ -62,7 +63,7 @@ set_window_size(struct screen *screen, struct sc_size new_size) {
// get the preferred display bounds (i.e. the screen bounds with some margins) // get the preferred display bounds (i.e. the screen bounds with some margins)
static bool static bool
get_preferred_display_bounds(struct sc_size *bounds) { get_preferred_display_bounds(struct size *bounds) {
SDL_Rect rect; SDL_Rect rect;
#ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS #ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r)) # define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r))
@ -80,7 +81,7 @@ get_preferred_display_bounds(struct sc_size *bounds) {
} }
static bool static bool
is_optimal_size(struct sc_size current_size, struct sc_size content_size) { is_optimal_size(struct size current_size, struct size content_size) {
// The size is optimal if we can recompute one dimension of the current // The size is optimal if we can recompute one dimension of the current
// size from the other // size from the other
return current_size.height == current_size.width * content_size.height return current_size.height == current_size.width * content_size.height
@ -94,16 +95,16 @@ is_optimal_size(struct sc_size current_size, struct sc_size content_size) {
// crops the black borders) // crops the black borders)
// - it keeps the aspect ratio // - it keeps the aspect ratio
// - it scales down to make it fit in the display_size // - it scales down to make it fit in the display_size
static struct sc_size static struct size
get_optimal_size(struct sc_size current_size, struct sc_size content_size) { get_optimal_size(struct size current_size, struct size content_size) {
if (content_size.width == 0 || content_size.height == 0) { if (content_size.width == 0 || content_size.height == 0) {
// avoid division by 0 // avoid division by 0
return current_size; return current_size;
} }
struct sc_size window_size; struct size window_size;
struct sc_size display_size; struct size display_size;
if (!get_preferred_display_bounds(&display_size)) { if (!get_preferred_display_bounds(&display_size)) {
// could not get display bounds, do not constraint the size // could not get display bounds, do not constraint the size
window_size.width = current_size.width; window_size.width = current_size.width;
@ -135,10 +136,10 @@ get_optimal_size(struct sc_size current_size, struct sc_size content_size) {
// initially, there is no current size, so use the frame size as current size // initially, there is no current size, so use the frame size as current size
// req_width and req_height, if not 0, are the sizes requested by the user // req_width and req_height, if not 0, are the sizes requested by the user
static inline struct sc_size static inline struct size
get_initial_optimal_size(struct sc_size content_size, uint16_t req_width, get_initial_optimal_size(struct size content_size, uint16_t req_width,
uint16_t req_height) { uint16_t req_height) {
struct sc_size window_size; struct size window_size;
if (!req_width && !req_height) { if (!req_width && !req_height) {
window_size = get_optimal_size(content_size, content_size); window_size = get_optimal_size(content_size, content_size);
} else { } else {
@ -166,9 +167,9 @@ screen_update_content_rect(struct screen *screen) {
int dh; int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh); SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
struct sc_size content_size = screen->content_size; struct size content_size = screen->content_size;
// The drawable size is the window size * the HiDPI scale // The drawable size is the window size * the HiDPI scale
struct sc_size drawable_size = {dw, dh}; struct size drawable_size = {dw, dh};
SDL_Rect *rect = &screen->rect; SDL_Rect *rect = &screen->rect;
@ -200,7 +201,7 @@ screen_update_content_rect(struct screen *screen) {
static inline SDL_Texture * static inline SDL_Texture *
create_texture(struct screen *screen) { create_texture(struct screen *screen) {
SDL_Renderer *renderer = screen->renderer; SDL_Renderer *renderer = screen->renderer;
struct sc_size size = screen->frame_size; struct size size = screen->frame_size;
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING, SDL_TEXTUREACCESS_STREAMING,
size.width, size.height); size.width, size.height);
@ -224,45 +225,6 @@ create_texture(struct screen *screen) {
return texture; return texture;
} }
// render the texture to the renderer
//
// Set the update_content_rect flag if the window or content size may have
// changed, so that the content rectangle is recomputed
static void
screen_render(struct screen *screen, bool update_content_rect) {
if (update_content_rect) {
screen_update_content_rect(screen);
}
SDL_RenderClear(screen->renderer);
if (screen->rotation == 0) {
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect);
} else {
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
// counterclockwise (to be consistent with --lock-video-orientation)
int cw_rotation = (4 - screen->rotation) % 4;
double angle = 90 * cw_rotation;
SDL_Rect *dstrect = NULL;
SDL_Rect rect;
if (screen->rotation & 1) {
rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2;
rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2;
rect.w = screen->rect.h;
rect.h = screen->rect.w;
dstrect = &rect;
} else {
assert(screen->rotation == 2);
dstrect = &screen->rect;
}
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
angle, NULL, 0);
}
SDL_RenderPresent(screen->renderer);
}
#if defined(__APPLE__) || defined(__WINDOWS__) #if defined(__APPLE__) || defined(__WINDOWS__)
# define CONTINUOUS_RESIZING_WORKAROUND # define CONTINUOUS_RESIZING_WORKAROUND
#endif #endif
@ -321,33 +283,17 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
(void) vb; (void) vb;
struct screen *screen = userdata; struct screen *screen = userdata;
// event_failed implies previous_skipped (the previous frame may not have
// been consumed if the event was not sent)
assert(!screen->event_failed || previous_skipped);
bool need_new_event;
if (previous_skipped) { if (previous_skipped) {
fps_counter_add_skipped_frame(&screen->fps_counter); fps_counter_add_skipped_frame(&screen->fps_counter);
// The EVENT_NEW_FRAME triggered for the previous frame will consume // The EVENT_NEW_FRAME triggered for the previous frame will consume
// this new frame instead, unless the previous event failed // this new frame instead
need_new_event = screen->event_failed;
} else { } else {
need_new_event = true;
}
if (need_new_event) {
static SDL_Event new_frame_event = { static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME, .type = EVENT_NEW_FRAME,
}; };
// Post the event on the UI thread // Post the event on the UI thread
int ret = SDL_PushEvent(&new_frame_event); SDL_PushEvent(&new_frame_event);
if (ret < 0) {
LOGW("Could not post new frame event: %s", SDL_GetError());
screen->event_failed = true;
} else {
screen->event_failed = false;
}
} }
} }
@ -357,7 +303,6 @@ screen_init(struct screen *screen, const struct screen_params *params) {
screen->has_frame = false; screen->has_frame = false;
screen->fullscreen = false; screen->fullscreen = false;
screen->maximized = false; screen->maximized = false;
screen->event_failed = false;
static const struct sc_video_buffer_callbacks cbs = { static const struct sc_video_buffer_callbacks cbs = {
.on_new_frame = sc_video_buffer_on_new_frame, .on_new_frame = sc_video_buffer_on_new_frame,
@ -386,12 +331,12 @@ screen_init(struct screen *screen, const struct screen_params *params) {
if (screen->rotation) { if (screen->rotation) {
LOGI("Initial display rotation set to %u", screen->rotation); LOGI("Initial display rotation set to %u", screen->rotation);
} }
struct sc_size content_size = struct size content_size =
get_rotated_size(screen->frame_size, screen->rotation); get_rotated_size(screen->frame_size, screen->rotation);
screen->content_size = content_size; screen->content_size = content_size;
struct sc_size window_size = struct size window_size = get_initial_optimal_size(content_size,
get_initial_optimal_size(content_size,params->window_width, params->window_width,
params->window_height); params->window_height);
uint32_t window_flags = SDL_WINDOW_HIDDEN uint32_t window_flags = SDL_WINDOW_HIDDEN
| SDL_WINDOW_RESIZABLE | SDL_WINDOW_RESIZABLE
@ -460,10 +405,10 @@ screen_init(struct screen *screen, const struct screen_params *params) {
LOGD("Trilinear filtering disabled (not an OpenGL renderer)"); LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
} }
SDL_Surface *icon = scrcpy_icon_load(); SDL_Surface *icon = read_xpm(icon_xpm);
if (icon) { if (icon) {
SDL_SetWindowIcon(screen->window, icon); SDL_SetWindowIcon(screen->window, icon);
scrcpy_icon_destroy(icon); SDL_FreeSurface(icon);
} else { } else {
LOGW("Could not load icon"); LOGW("Could not load icon");
} }
@ -564,10 +509,10 @@ screen_destroy(struct screen *screen) {
} }
static void static void
resize_for_content(struct screen *screen, struct sc_size old_content_size, resize_for_content(struct screen *screen, struct size old_content_size,
struct sc_size new_content_size) { struct size new_content_size) {
struct sc_size window_size = get_window_size(screen); struct size window_size = get_window_size(screen);
struct sc_size target_size = { struct size target_size = {
.width = (uint32_t) window_size.width * new_content_size.width .width = (uint32_t) window_size.width * new_content_size.width
/ old_content_size.width, / old_content_size.width,
.height = (uint32_t) window_size.height * new_content_size.height .height = (uint32_t) window_size.height * new_content_size.height
@ -578,7 +523,7 @@ resize_for_content(struct screen *screen, struct sc_size old_content_size,
} }
static void static void
set_content_size(struct screen *screen, struct sc_size new_content_size) { set_content_size(struct screen *screen, struct size new_content_size) {
if (!screen->fullscreen && !screen->maximized) { if (!screen->fullscreen && !screen->maximized) {
resize_for_content(screen, screen->content_size, new_content_size); resize_for_content(screen, screen->content_size, new_content_size);
} else if (!screen->resize_pending) { } else if (!screen->resize_pending) {
@ -609,7 +554,7 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
return; return;
} }
struct sc_size new_content_size = struct size new_content_size =
get_rotated_size(screen->frame_size, rotation); get_rotated_size(screen->frame_size, rotation);
set_content_size(screen, new_content_size); set_content_size(screen, new_content_size);
@ -622,7 +567,7 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
// recreate the texture and resize the window if the frame size has changed // recreate the texture and resize the window if the frame size has changed
static bool static bool
prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) { prepare_for_frame(struct screen *screen, struct size new_frame_size) {
if (screen->frame_size.width != new_frame_size.width if (screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) { || screen->frame_size.height != new_frame_size.height) {
// frame dimension changed, destroy texture // frame dimension changed, destroy texture
@ -630,7 +575,7 @@ prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) {
screen->frame_size = new_frame_size; screen->frame_size = new_frame_size;
struct sc_size new_content_size = struct size new_content_size =
get_rotated_size(new_frame_size, screen->rotation); get_rotated_size(new_frame_size, screen->rotation);
set_content_size(screen, new_content_size); set_content_size(screen, new_content_size);
@ -671,7 +616,7 @@ screen_update_frame(struct screen *screen) {
fps_counter_add_rendered_frame(&screen->fps_counter); fps_counter_add_rendered_frame(&screen->fps_counter);
struct sc_size new_frame_size = {frame->width, frame->height}; struct size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) { if (!prepare_for_frame(screen, new_frame_size)) {
return false; return false;
} }
@ -681,6 +626,40 @@ screen_update_frame(struct screen *screen) {
return true; return true;
} }
void
screen_render(struct screen *screen, bool update_content_rect) {
if (update_content_rect) {
screen_update_content_rect(screen);
}
SDL_RenderClear(screen->renderer);
if (screen->rotation == 0) {
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect);
} else {
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
// counterclockwise (to be consistent with --lock-video-orientation)
int cw_rotation = (4 - screen->rotation) % 4;
double angle = 90 * cw_rotation;
SDL_Rect *dstrect = NULL;
SDL_Rect rect;
if (screen->rotation & 1) {
rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2;
rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2;
rect.w = screen->rect.h;
rect.h = screen->rect.w;
dstrect = &rect;
} else {
assert(screen->rotation == 2);
dstrect = &screen->rect;
}
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
angle, NULL, 0);
}
SDL_RenderPresent(screen->renderer);
}
void void
screen_switch_fullscreen(struct screen *screen) { screen_switch_fullscreen(struct screen *screen) {
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP; uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
@ -704,10 +683,10 @@ screen_resize_to_fit(struct screen *screen) {
return; return;
} }
struct sc_point point = get_window_position(screen); struct point point = get_window_position(screen);
struct sc_size window_size = get_window_size(screen); struct size window_size = get_window_size(screen);
struct sc_size optimal_size = struct size optimal_size =
get_optimal_size(window_size, screen->content_size); get_optimal_size(window_size, screen->content_size);
// Center the window related to the device screen // Center the window related to the device screen
@ -733,7 +712,7 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
screen->maximized = false; screen->maximized = false;
} }
struct sc_size content_size = screen->content_size; struct size content_size = screen->content_size;
SDL_SetWindowSize(screen->window, content_size.width, content_size.height); SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
LOGD("Resized to pixel-perfect: %ux%u", content_size.width, LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
content_size.height); content_size.height);
@ -788,7 +767,7 @@ screen_handle_event(struct screen *screen, SDL_Event *event) {
return false; return false;
} }
struct sc_point struct point
screen_convert_drawable_to_frame_coords(struct screen *screen, screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) { int32_t x, int32_t y) {
unsigned rotation = screen->rotation; unsigned rotation = screen->rotation;
@ -802,7 +781,7 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h; y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
// rotate // rotate
struct sc_point result; struct point result;
switch (rotation) { switch (rotation) {
case 0: case 0:
result.x = x; result.x = x;
@ -825,7 +804,7 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
return result; return result;
} }
struct sc_point struct point
screen_convert_window_to_frame_coords(struct screen *screen, screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) { int32_t x, int32_t y) {
screen_hidpi_scale_coords(screen, &x, &y); screen_hidpi_scale_coords(screen, &x, &y);

View File

@ -27,13 +27,13 @@ struct screen {
SDL_Renderer *renderer; SDL_Renderer *renderer;
SDL_Texture *texture; SDL_Texture *texture;
struct sc_opengl gl; struct sc_opengl gl;
struct sc_size frame_size; struct size frame_size;
struct sc_size content_size; // rotated frame_size struct size content_size; // rotated frame_size
bool resize_pending; // resize requested while fullscreen or maximized bool resize_pending; // resize requested while fullscreen or maximized
// The content size the last time the window was not maximized or // The content size the last time the window was not maximized or
// fullscreen (meaningful only when resize_pending is true) // fullscreen (meaningful only when resize_pending is true)
struct sc_size windowed_content_size; struct size windowed_content_size;
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise) // client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
unsigned rotation; unsigned rotation;
@ -44,14 +44,12 @@ struct screen {
bool maximized; bool maximized;
bool mipmaps; bool mipmaps;
bool event_failed; // in case SDL_PushEvent() returned an error
AVFrame *frame; AVFrame *frame;
}; };
struct screen_params { struct screen_params {
const char *window_title; const char *window_title;
struct sc_size frame_size; struct size frame_size;
bool always_on_top; bool always_on_top;
int16_t window_x; int16_t window_x;
@ -93,6 +91,13 @@ screen_destroy(struct screen *screen);
void void
screen_hide_window(struct screen *screen); screen_hide_window(struct screen *screen);
// render the texture to the renderer
//
// Set the update_content_rect flag if the window or content size may have
// changed, so that the content rectangle is recomputed
void
screen_render(struct screen *screen, bool update_content_rect);
// switch the fullscreen mode // switch the fullscreen mode
void void
screen_switch_fullscreen(struct screen *screen); screen_switch_fullscreen(struct screen *screen);
@ -115,13 +120,13 @@ screen_handle_event(struct screen *screen, SDL_Event *event);
// convert point from window coordinates to frame coordinates // convert point from window coordinates to frame coordinates
// x and y are expressed in pixels // x and y are expressed in pixels
struct sc_point struct point
screen_convert_window_to_frame_coords(struct screen *screen, screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y); int32_t x, int32_t y);
// convert point from drawable coordinates to frame coordinates // convert point from drawable coordinates to frame coordinates
// x and y are expressed in pixels // x and y are expressed in pixels
struct sc_point struct point
screen_convert_drawable_to_frame_coords(struct screen *screen, screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y); int32_t x, int32_t y);

View File

@ -3,21 +3,21 @@
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
#include <libgen.h>
#include <stdio.h> #include <stdio.h>
#include <SDL2/SDL_timer.h> #include <SDL2/SDL_timer.h>
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
#include "adb.h" #include "adb.h"
#include "util/file.h"
#include "util/log.h" #include "util/log.h"
#include "util/net_intr.h" #include "util/net.h"
#include "util/process_intr.h" #include "util/str_util.h"
#include "util/str.h"
#define SC_SERVER_FILENAME "scrcpy-server" #define SOCKET_NAME "scrcpy"
#define SERVER_FILENAME "scrcpy-server"
#define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME #define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FILENAME
#define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar" #define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
static char * static char *
get_server_path(void) { get_server_path(void) {
@ -29,7 +29,7 @@ get_server_path(void) {
if (server_path_env) { if (server_path_env) {
// if the envvar is set, use it // if the envvar is set, use it
#ifdef __WINDOWS__ #ifdef __WINDOWS__
char *server_path = sc_str_from_wchars(server_path_env); char *server_path = utf8_from_wide_char(server_path_env);
#else #else
char *server_path = strdup(server_path_env); char *server_path = strdup(server_path_env);
#endif #endif
@ -42,80 +42,207 @@ get_server_path(void) {
} }
#ifndef PORTABLE #ifndef PORTABLE
LOGD("Using server: " SC_SERVER_PATH_DEFAULT); LOGD("Using server: " DEFAULT_SERVER_PATH);
char *server_path = strdup(SC_SERVER_PATH_DEFAULT); char *server_path = strdup(DEFAULT_SERVER_PATH);
if (!server_path) { if (!server_path) {
LOGE("Could not allocate memory"); LOGE("Could not allocate memory");
return NULL; return NULL;
} }
// the absolute path is hardcoded
return server_path;
#else #else
char *server_path = sc_file_get_local_path(SC_SERVER_FILENAME);
if (!server_path) { // use scrcpy-server in the same directory as the executable
LOGE("Could not get local file path, " char *executable_path = get_executable_path();
"using " SC_SERVER_FILENAME " from current directory"); if (!executable_path) {
return strdup(SC_SERVER_FILENAME); LOGE("Could not get executable path, "
"using " SERVER_FILENAME " from current directory");
// not found, use current directory
return strdup(SERVER_FILENAME);
} }
// dirname() does not work correctly everywhere, so get the parent
// directory manually.
// See <https://github.com/Genymobile/scrcpy/issues/2619>
char *p = strrchr(executable_path, PATH_SEPARATOR);
if (!p) {
LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')",
executable_path, PATH_SEPARATOR);
free(executable_path);
return strdup(SERVER_FILENAME);
}
*p = '\0'; // modify executable_path in place
char *dir = executable_path;
size_t dirlen = strlen(dir);
// sizeof(SERVER_FILENAME) gives statically the size including the null byte
size_t len = dirlen + 1 + sizeof(SERVER_FILENAME);
char *server_path = malloc(len);
if (!server_path) {
LOGE("Could not alloc server path string, "
"using " SERVER_FILENAME " from current directory");
free(executable_path);
return strdup(SERVER_FILENAME);
}
memcpy(server_path, dir, dirlen);
server_path[dirlen] = PATH_SEPARATOR;
memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME));
// the final null byte has been copied with SERVER_FILENAME
free(executable_path);
LOGD("Using server (portable): %s", server_path); LOGD("Using server (portable): %s", server_path);
#endif
return server_path; return server_path;
} #endif
static void
sc_server_params_destroy(struct sc_server_params *params) {
// The server stores a copy of the params provided by the user
free((char *) params->serial);
free((char *) params->crop);
free((char *) params->codec_options);
free((char *) params->encoder_name);
} }
static bool static bool
sc_server_params_copy(struct sc_server_params *dst, push_server(const char *serial) {
const struct sc_server_params *src) {
*dst = *src;
// The params reference user-allocated memory, so we must copy them to
// handle them from another thread
#define COPY(FIELD) \
dst->FIELD = NULL; \
if (src->FIELD) { \
dst->FIELD = strdup(src->FIELD); \
if (!dst->FIELD) { \
goto error; \
} \
}
COPY(serial);
COPY(crop);
COPY(codec_options);
COPY(encoder_name);
#undef COPY
return true;
error:
sc_server_params_destroy(dst);
return false;
}
static bool
push_server(struct sc_intr *intr, const char *serial) {
char *server_path = get_server_path(); char *server_path = get_server_path();
if (!server_path) { if (!server_path) {
return false; return false;
} }
if (!sc_file_is_regular(server_path)) { if (!is_regular_file(server_path)) {
LOGE("'%s' does not exist or is not a regular file\n", server_path); LOGE("'%s' does not exist or is not a regular file\n", server_path);
free(server_path); free(server_path);
return false; return false;
} }
bool ok = adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH, process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH);
SC_STDERR);
free(server_path); free(server_path);
return ok; return process_check_success(process, "adb push", true);
}
static bool
enable_tunnel_reverse(const char *serial, uint16_t local_port) {
process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
return process_check_success(process, "adb reverse", true);
}
static bool
disable_tunnel_reverse(const char *serial) {
process_t process = adb_reverse_remove(serial, SOCKET_NAME);
return process_check_success(process, "adb reverse --remove", true);
}
static bool
enable_tunnel_forward(const char *serial, uint16_t local_port) {
process_t process = adb_forward(serial, local_port, SOCKET_NAME);
return process_check_success(process, "adb forward", true);
}
static bool
disable_tunnel_forward(const char *serial, uint16_t local_port) {
process_t process = adb_forward_remove(serial, local_port);
return process_check_success(process, "adb forward --remove", true);
}
static 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 socket_t
listen_on_port(uint16_t port) {
#define IPV4_LOCALHOST 0x7F000001
return net_listen(IPV4_LOCALHOST, port, 1);
}
static bool
enable_tunnel_reverse_any_port(struct server *server,
struct sc_port_range port_range) {
uint16_t port = port_range.first;
for (;;) {
if (!enable_tunnel_reverse(server->serial, port)) {
// the command itself failed, it will fail on any port
return false;
}
// At the application level, the device part is "the server" because it
// serves video stream and control. However, at the network level, the
// client listens and the server connects to the client. That way, the
// client can listen before starting the server app, so there is no
// need to try to connect until the server socket is listening on the
// device.
server->server_socket = listen_on_port(port);
if (server->server_socket != INVALID_SOCKET) {
// success
server->local_port = port;
return true;
}
// failure, disable tunnel and try another port
if (!disable_tunnel_reverse(server->serial)) {
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
}
// check before incrementing to avoid overflow on port 65535
if (port < port_range.last) {
LOGW("Could not listen on port %" PRIu16", retrying on %" PRIu16,
port, (uint16_t) (port + 1));
port++;
continue;
}
if (port_range.first == port_range.last) {
LOGE("Could not listen on port %" PRIu16, port_range.first);
} else {
LOGE("Could not listen on any port in range %" PRIu16 ":%" PRIu16,
port_range.first, port_range.last);
}
return false;
}
}
static bool
enable_tunnel_forward_any_port(struct server *server,
struct sc_port_range port_range) {
server->tunnel_forward = true;
uint16_t port = port_range.first;
for (;;) {
if (enable_tunnel_forward(server->serial, port)) {
// success
server->local_port = port;
return true;
}
if (port < port_range.last) {
LOGW("Could not forward port %" PRIu16", retrying on %" PRIu16,
port, (uint16_t) (port + 1));
port++;
continue;
}
if (port_range.first == port_range.last) {
LOGE("Could not forward port %" PRIu16, port_range.first);
} else {
LOGE("Could not forward any port in range %" PRIu16 ":%" PRIu16,
port_range.first, port_range.last);
}
return false;
}
}
static bool
enable_tunnel_any_port(struct server *server, struct sc_port_range port_range,
bool force_adb_forward) {
if (!force_adb_forward) {
// Attempt to use "adb reverse"
if (enable_tunnel_reverse_any_port(server, port_range)) {
return true;
}
// if "adb reverse" does not work (e.g. over "adb connect"), it
// fallbacks to "adb forward", so the app socket is the client
LOGW("'adb reverse' failed, fallback to 'adb forward'");
}
return enable_tunnel_forward_any_port(server, port_range);
} }
static const char * static const char *
@ -137,11 +264,8 @@ log_level_to_server_string(enum sc_log_level level) {
} }
} }
static sc_pid static process_t
execute_server(struct sc_server *server, execute_server(struct server *server, const struct server_params *params) {
const struct sc_server_params *params) {
const char *serial = server->params.serial;
char max_size_string[6]; char max_size_string[6];
char bit_rate_string[11]; char bit_rate_string[11];
char max_fps_string[6]; char max_fps_string[6];
@ -155,7 +279,7 @@ execute_server(struct sc_server *server,
sprintf(display_id_string, "%"PRIu32, params->display_id); sprintf(display_id_string, "%"PRIu32, params->display_id);
const char *const cmd[] = { const char *const cmd[] = {
"shell", "shell",
"CLASSPATH=" SC_DEVICE_SERVER_PATH, "CLASSPATH=" DEVICE_SERVER_PATH,
"app_process", "app_process",
#ifdef SERVER_DEBUGGER #ifdef SERVER_DEBUGGER
# define SERVER_DEBUGGER_PORT "5005" # define SERVER_DEBUGGER_PORT "5005"
@ -177,7 +301,7 @@ execute_server(struct sc_server *server,
bit_rate_string, bit_rate_string,
max_fps_string, max_fps_string,
lock_video_orientation_string, lock_video_orientation_string,
server->tunnel.forward ? "true" : "false", server->tunnel_forward ? "true" : "false",
params->crop ? params->crop : "-", params->crop ? params->crop : "-",
"true", // always send frame meta (packet boundaries + timestamp) "true", // always send frame meta (packet boundaries + timestamp)
params->control ? "true" : "false", params->control ? "true" : "false",
@ -199,367 +323,276 @@ execute_server(struct sc_server *server,
// Port: 5005 // Port: 5005
// Then click on "Debug" // Then click on "Debug"
#endif #endif
// Inherit both stdout and stderr (all server logs are printed to stdout) return adb_execute(server->serial, cmd, ARRAY_LEN(cmd));
return adb_execute(serial, cmd, ARRAY_LEN(cmd), SC_STDOUT | SC_STDERR);
} }
static bool static socket_t
connect_and_read_byte(struct sc_intr *intr, sc_socket socket, uint16_t port) { connect_and_read_byte(uint16_t port) {
bool ok = net_connect_intr(intr, socket, IPV4_LOCALHOST, port); socket_t socket = net_connect(IPV4_LOCALHOST, port);
if (!ok) { if (socket == INVALID_SOCKET) {
return false; return INVALID_SOCKET;
} }
char byte; char byte;
// the connection may succeed even if the server behind the "adb tunnel" // the connection may succeed even if the server behind the "adb tunnel"
// is not listening, so read one byte to detect a working connection // is not listening, so read one byte to detect a working connection
if (net_recv_intr(intr, socket, &byte, 1) != 1) { if (net_recv(socket, &byte, 1) != 1) {
// the server is not listening yet behind the adb tunnel // the server is not listening yet behind the adb tunnel
return false; net_close(socket);
return INVALID_SOCKET;
} }
return true;
}
static sc_socket
connect_to_server(struct sc_server *server, uint32_t attempts, sc_tick delay) {
uint16_t port = server->tunnel.local_port;
do {
LOGD("Remaining connection attempts: %d", (int) attempts);
sc_socket socket = net_socket();
if (socket != SC_SOCKET_NONE) {
bool ok = connect_and_read_byte(&server->intr, socket, port);
if (ok) {
// it worked!
return socket; return socket;
} }
net_close(socket); static socket_t
connect_to_server(uint16_t port, uint32_t attempts, uint32_t 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 (sc_intr_is_interrupted(&server->intr)) {
// Stop immediately
break;
}
if (attempts) { if (attempts) {
sc_mutex_lock(&server->mutex); SDL_Delay(delay);
sc_tick deadline = sc_tick_now() + delay;
bool timed_out = false;
while (!server->stopped && !timed_out) {
timed_out = !sc_cond_timedwait(&server->cond_stopped,
&server->mutex, deadline);
}
bool stopped = server->stopped;
sc_mutex_unlock(&server->mutex);
if (stopped) {
LOGI("Connection attempt stopped");
break;
}
} }
} while (--attempts > 0); } while (--attempts > 0);
return SC_SOCKET_NONE; return INVALID_SOCKET;
}
bool
sc_server_init(struct sc_server *server, const struct sc_server_params *params,
const struct sc_server_callbacks *cbs, void *cbs_userdata) {
bool ok = sc_server_params_copy(&server->params, params);
if (!ok) {
LOGE("Could not copy server params");
return false;
}
ok = sc_mutex_init(&server->mutex);
if (!ok) {
LOGE("Could not create server mutex");
sc_server_params_destroy(&server->params);
return false;
}
ok = sc_cond_init(&server->cond_stopped);
if (!ok) {
LOGE("Could not create server cond_stopped");
sc_mutex_destroy(&server->mutex);
sc_server_params_destroy(&server->params);
return false;
}
ok = sc_intr_init(&server->intr);
if (!ok) {
LOGE("Could not create intr");
sc_cond_destroy(&server->cond_stopped);
sc_mutex_destroy(&server->mutex);
sc_server_params_destroy(&server->params);
return false;
}
server->stopped = false;
server->video_socket = SC_SOCKET_NONE;
server->control_socket = SC_SOCKET_NONE;
sc_adb_tunnel_init(&server->tunnel);
assert(cbs);
assert(cbs->on_connection_failed);
assert(cbs->on_connected);
assert(cbs->on_disconnected);
server->cbs = cbs;
server->cbs_userdata = cbs_userdata;
return true;
}
static bool
device_read_info(struct sc_intr *intr, sc_socket device_socket,
struct sc_server_info *info) {
unsigned char buf[SC_DEVICE_NAME_FIELD_LENGTH + 4];
ssize_t r = net_recv_all_intr(intr, device_socket, buf, sizeof(buf));
if (r < SC_DEVICE_NAME_FIELD_LENGTH + 4) {
LOGE("Could not retrieve device information");
return false;
}
// in case the client sends garbage
buf[SC_DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
memcpy(info->device_name, (char *) buf, sizeof(info->device_name));
info->frame_size.width = (buf[SC_DEVICE_NAME_FIELD_LENGTH] << 8)
| buf[SC_DEVICE_NAME_FIELD_LENGTH + 1];
info->frame_size.height = (buf[SC_DEVICE_NAME_FIELD_LENGTH + 2] << 8)
| buf[SC_DEVICE_NAME_FIELD_LENGTH + 3];
return true;
}
static bool
sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
struct sc_adb_tunnel *tunnel = &server->tunnel;
assert(tunnel->enabled);
const char *serial = server->params.serial;
sc_socket video_socket = SC_SOCKET_NONE;
sc_socket control_socket = SC_SOCKET_NONE;
if (!tunnel->forward) {
video_socket = net_accept_intr(&server->intr, tunnel->server_socket);
if (video_socket == SC_SOCKET_NONE) {
goto fail;
}
control_socket = net_accept_intr(&server->intr, tunnel->server_socket);
if (control_socket == SC_SOCKET_NONE) {
goto fail;
}
} else {
uint32_t attempts = 100;
sc_tick delay = SC_TICK_FROM_MS(100);
video_socket = connect_to_server(server, attempts, delay);
if (video_socket == SC_SOCKET_NONE) {
goto fail;
}
// we know that the device is listening, we don't need several attempts
control_socket = net_socket();
if (control_socket == SC_SOCKET_NONE) {
goto fail;
}
bool ok = net_connect_intr(&server->intr, control_socket,
IPV4_LOCALHOST, tunnel->local_port);
if (!ok) {
goto fail;
}
}
// we don't need the adb tunnel anymore
sc_adb_tunnel_close(tunnel, &server->intr, serial);
// The sockets will be closed on stop if device_read_info() fails
bool ok = device_read_info(&server->intr, video_socket, info);
if (!ok) {
goto fail;
}
assert(video_socket != SC_SOCKET_NONE);
assert(control_socket != SC_SOCKET_NONE);
server->video_socket = video_socket;
server->control_socket = control_socket;
return true;
fail:
if (video_socket != SC_SOCKET_NONE) {
if (!net_close(video_socket)) {
LOGW("Could not close video socket");
}
}
if (control_socket != SC_SOCKET_NONE) {
if (!net_close(control_socket)) {
LOGW("Could not close control socket");
}
}
// Always leave this function with tunnel disabled
sc_adb_tunnel_close(tunnel, &server->intr, serial);
return false;
} }
static void static void
sc_server_on_terminated(void *userdata) { close_socket(socket_t socket) {
struct sc_server *server = userdata; assert(socket != INVALID_SOCKET);
net_shutdown(socket, SHUT_RDWR);
// If the server process dies before connecting to the server socket, if (!net_close(socket)) {
// then the client will be stuck forever on accept(). To avoid the problem, LOGW("Could not close socket");
// wake up the accept() call (or any other) when the server dies, like on }
// stop() (it is safe to call interrupt() twice).
sc_intr_interrupt(&server->intr);
server->cbs->on_disconnected(server, server->cbs_userdata);
LOGD("Server terminated");
} }
static bool bool
sc_server_fill_serial(struct sc_server *server) { server_init(struct server *server) {
// Retrieve the actual device immediately if not provided, so that all server->serial = NULL;
// future adb commands are executed for this specific device, even if other server->process = PROCESS_NONE;
// devices are connected afterwards (without "more than one atomic_flag_clear_explicit(&server->server_socket_closed,
// device/emulator" error) memory_order_relaxed);
if (!server->params.serial) {
// The serial is owned by sc_server_params, and will be freed on destroy bool ok = sc_mutex_init(&server->mutex);
server->params.serial = adb_get_serialno(&server->intr, SC_STDERR); if (!ok) {
if (!server->params.serial) {
LOGE("Could not get device serial");
return false; return false;
} }
ok = sc_cond_init(&server->process_terminated_cond);
if (!ok) {
sc_mutex_destroy(&server->mutex);
return false;
} }
server->process_terminated = false;
server->server_socket = INVALID_SOCKET;
server->video_socket = INVALID_SOCKET;
server->control_socket = INVALID_SOCKET;
server->local_port = 0;
server->tunnel_enabled = false;
server->tunnel_forward = false;
return true; return true;
} }
static int static int
run_server(void *data) { run_wait_server(void *data) {
struct sc_server *server = data; struct server *server = data;
process_wait(server->process, false); // ignore exit code
if (!sc_server_fill_serial(server)) { sc_mutex_lock(&server->mutex);
goto error_connection_failed; server->process_terminated = true;
sc_cond_signal(&server->process_terminated_cond);
sc_mutex_unlock(&server->mutex);
// no need for synchronization, server_socket is initialized before this
// thread was created
if (server->server_socket != INVALID_SOCKET
&& !atomic_flag_test_and_set(&server->server_socket_closed)) {
// On Linux, accept() is unblocked by shutdown(), but on Windows, it is
// unblocked by closesocket(). Therefore, call both (close_socket()).
close_socket(server->server_socket);
}
LOGD("Server terminated");
return 0;
} }
const struct sc_server_params *params = &server->params; bool
server_start(struct server *server, const struct server_params *params) {
LOGD("Device serial: %s", params->serial); if (params->serial) {
server->serial = strdup(params->serial);
bool ok = push_server(&server->intr, params->serial); if (!server->serial) {
if (!ok) { return false;
goto error_connection_failed; }
} }
LOGI("Server pushed"); if (!push_server(params->serial)) {
/* server->serial will be freed on server_destroy() */
return false;
}
ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, params->serial, if (!enable_tunnel_any_port(server, params->port_range,
params->port_range, params->force_adb_forward); params->force_adb_forward)) {
if (!ok) { return false;
goto error_connection_failed;
} }
// server will connect to our server socket // server will connect to our server socket
sc_pid pid = execute_server(server, params); server->process = execute_server(server, params);
if (pid == SC_PROCESS_NONE) { if (server->process == PROCESS_NONE) {
sc_adb_tunnel_close(&server->tunnel, &server->intr, params->serial); goto error;
goto error_connection_failed;
} }
static const struct sc_process_listener listener = { // If the server process dies before connecting to the server socket, then
.on_terminated = sc_server_on_terminated, // the client will be stuck forever on accept(). To avoid the problem, we
}; // must be able to wake up the accept() call when the server dies. To keep
struct sc_process_observer observer; // things simple and multiplatform, just spawn a new thread waiting for the
ok = sc_process_observer_init(&observer, pid, &listener, server); // server process and calling shutdown()/close() on the server socket if
// necessary to wake up any accept() blocking call.
bool ok = sc_thread_create(&server->wait_server_thread, run_wait_server,
"wait-server", server);
if (!ok) { if (!ok) {
sc_process_terminate(pid); process_terminate(server->process);
sc_process_wait(pid, true); // ignore exit code process_wait(server->process, true); // ignore exit code
sc_adb_tunnel_close(&server->tunnel, &server->intr, params->serial); goto error;
goto error_connection_failed;
} }
ok = sc_server_connect_to(server, &server->info); server->tunnel_enabled = true;
// The tunnel is always closed by server_connect_to()
if (!ok) { return true;
sc_process_terminate(pid);
sc_process_wait(pid, true); // ignore exit code error:
sc_process_observer_join(&observer); if (!server->tunnel_forward) {
sc_process_observer_destroy(&observer); bool was_closed =
goto error_connection_failed; atomic_flag_test_and_set(&server->server_socket_closed);
// the thread is not started, the flag could not be already set
assert(!was_closed);
(void) was_closed;
close_socket(server->server_socket);
}
disable_tunnel(server);
return false;
} }
// Now connected static bool
server->cbs->on_connected(server, server->cbs_userdata); device_read_info(socket_t device_socket, char *device_name, struct size *size) {
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
// Wait for server_stop() ssize_t r = net_recv_all(device_socket, buf, sizeof(buf));
sc_mutex_lock(&server->mutex); if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
while (!server->stopped) { LOGE("Could not retrieve device information");
sc_cond_wait(&server->cond_stopped, &server->mutex); return false;
}
// in case the client sends garbage
buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
// strcpy is safe here, since name contains at least
// DEVICE_NAME_FIELD_LENGTH bytes and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
strcpy(device_name, (char *) buf);
size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8)
| buf[DEVICE_NAME_FIELD_LENGTH + 1];
size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8)
| buf[DEVICE_NAME_FIELD_LENGTH + 3];
return true;
}
bool
server_connect_to(struct server *server, char *device_name, struct size *size) {
if (!server->tunnel_forward) {
server->video_socket = net_accept(server->server_socket);
if (server->video_socket == INVALID_SOCKET) {
return false;
}
server->control_socket = net_accept(server->server_socket);
if (server->control_socket == INVALID_SOCKET) {
// the video_socket will be cleaned up on destroy
return false;
}
// we don't need the server socket anymore
if (!atomic_flag_test_and_set(&server->server_socket_closed)) {
// close it from here
close_socket(server->server_socket);
// otherwise, it is closed by run_wait_server()
}
} else {
uint32_t attempts = 100;
uint32_t delay = 100; // ms
server->video_socket =
connect_to_server(server->local_port, attempts, delay);
if (server->video_socket == INVALID_SOCKET) {
return false;
}
// we know that the device is listening, we don't need several attempts
server->control_socket =
net_connect(IPV4_LOCALHOST, server->local_port);
if (server->control_socket == INVALID_SOCKET) {
return false;
}
}
// we don't need the adb tunnel anymore
disable_tunnel(server); // ignore failure
server->tunnel_enabled = false;
// The sockets will be closed on stop if device_read_info() fails
return device_read_info(server->video_socket, device_name, size);
}
void
server_stop(struct server *server) {
if (server->server_socket != INVALID_SOCKET
&& !atomic_flag_test_and_set(&server->server_socket_closed)) {
close_socket(server->server_socket);
}
if (server->video_socket != INVALID_SOCKET) {
close_socket(server->video_socket);
}
if (server->control_socket != INVALID_SOCKET) {
close_socket(server->control_socket);
}
assert(server->process != PROCESS_NONE);
if (server->tunnel_enabled) {
// ignore failure
disable_tunnel(server);
} }
sc_mutex_unlock(&server->mutex);
// Give some delay for the server to terminate properly // Give some delay for the server to terminate properly
sc_mutex_lock(&server->mutex);
bool signaled = false;
if (!server->process_terminated) {
#define WATCHDOG_DELAY SC_TICK_FROM_SEC(1) #define WATCHDOG_DELAY SC_TICK_FROM_SEC(1)
sc_tick deadline = sc_tick_now() + WATCHDOG_DELAY; signaled = sc_cond_timedwait(&server->process_terminated_cond,
bool terminated = sc_process_observer_timedwait(&observer, deadline); &server->mutex,
sc_tick_now() + WATCHDOG_DELAY);
}
sc_mutex_unlock(&server->mutex);
// After this delay, kill the server if it's not dead already. // After this delay, kill the server if it's not dead already.
// On some devices, closing the sockets is not sufficient to wake up the // On some devices, closing the sockets is not sufficient to wake up the
// blocking calls while the device is asleep. // blocking calls while the device is asleep.
if (!terminated) { if (!signaled) {
// The process may have terminated since the check, but it is not // The process is terminated, but not reaped (closed) yet, so its PID
// reaped (closed) yet, so its PID is still valid, and it is ok to call // is still valid.
// sc_process_terminate() even in that case.
LOGW("Killing the server..."); LOGW("Killing the server...");
sc_process_terminate(pid); process_terminate(server->process);
} }
sc_process_observer_join(&observer); sc_thread_join(&server->wait_server_thread, NULL);
sc_process_observer_destroy(&observer); process_close(server->process);
sc_process_close(pid);
return 0;
error_connection_failed:
server->cbs->on_connection_failed(server, server->cbs_userdata);
return -1;
}
bool
sc_server_start(struct sc_server *server) {
bool ok = sc_thread_create(&server->thread, run_server, "server", server);
if (!ok) {
LOGE("Could not create server thread");
return false;
}
return true;
} }
void void
sc_server_stop(struct sc_server *server) { server_destroy(struct server *server) {
sc_mutex_lock(&server->mutex); free(server->serial);
server->stopped = true; sc_cond_destroy(&server->process_terminated_cond);
sc_cond_signal(&server->cond_stopped);
sc_intr_interrupt(&server->intr);
sc_mutex_unlock(&server->mutex);
sc_thread_join(&server->thread, NULL);
}
void
sc_server_destroy(struct sc_server *server) {
sc_server_params_destroy(&server->params);
sc_intr_destroy(&server->intr);
sc_cond_destroy(&server->cond_stopped);
sc_mutex_destroy(&server->mutex); sc_mutex_destroy(&server->mutex);
} }

View File

@ -8,21 +8,31 @@
#include <stdint.h> #include <stdint.h>
#include "adb.h" #include "adb.h"
#include "adb_tunnel.h"
#include "coords.h" #include "coords.h"
#include "options.h" #include "scrcpy.h"
#include "util/intr.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h" #include "util/thread.h"
#define SC_DEVICE_NAME_FIELD_LENGTH 64 struct server {
struct sc_server_info { char *serial;
char device_name[SC_DEVICE_NAME_FIELD_LENGTH]; process_t process;
struct sc_size frame_size; sc_thread wait_server_thread;
atomic_flag server_socket_closed;
sc_mutex mutex;
sc_cond process_terminated_cond;
bool process_terminated;
socket_t server_socket; // only used if !tunnel_forward
socket_t video_socket;
socket_t control_socket;
uint16_t local_port; // selected from port_range
bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
}; };
struct sc_server_params { struct server_params {
const char *serial; const char *serial;
enum sc_log_level log_level; enum sc_log_level log_level;
const char *crop; const char *crop;
@ -41,62 +51,26 @@ struct sc_server_params {
bool power_off_on_close; bool power_off_on_close;
}; };
struct sc_server { // init default values
// The internal allocated strings are copies owned by the server
struct sc_server_params params;
sc_thread thread;
struct sc_server_info info; // initialized once connected
sc_mutex mutex;
sc_cond cond_stopped;
bool stopped;
struct sc_intr intr;
struct sc_adb_tunnel tunnel;
sc_socket video_socket;
sc_socket control_socket;
const struct sc_server_callbacks *cbs;
void *cbs_userdata;
};
struct sc_server_callbacks {
/**
* Called when the server failed to connect
*
* If it is called, then on_connected() and on_disconnected() will never be
* called.
*/
void (*on_connection_failed)(struct sc_server *server, void *userdata);
/**
* Called on server connection
*/
void (*on_connected)(struct sc_server *server, void *userdata);
/**
* Called on server disconnection (after it has been connected)
*/
void (*on_disconnected)(struct sc_server *server, void *userdata);
};
// init the server with the given params
bool bool
sc_server_init(struct sc_server *server, const struct sc_server_params *params, server_init(struct server *server);
const struct sc_server_callbacks *cbs, void *cbs_userdata);
// start the server asynchronously // push, enable tunnel et start the server
bool bool
sc_server_start(struct sc_server *server); server_start(struct server *server, const struct server_params *params);
#define DEVICE_NAME_FIELD_LENGTH 64
// block until the communication with the server is established
// device_name must point to a buffer of at least DEVICE_NAME_FIELD_LENGTH bytes
bool
server_connect_to(struct server *server, char *device_name, struct size *size);
// disconnect and kill the server process // disconnect and kill the server process
void void
sc_server_stop(struct sc_server *server); server_stop(struct server *server);
// close and release sockets // close and release sockets
void void
sc_server_destroy(struct sc_server *server); server_destroy(struct server *server);
#endif #endif

View File

@ -260,7 +260,7 @@ end:
} }
void void
stream_init(struct stream *stream, sc_socket socket, stream_init(struct stream *stream, socket_t socket,
const struct stream_callbacks *cbs, void *cbs_userdata) { const struct stream_callbacks *cbs, void *cbs_userdata) {
stream->socket = socket; stream->socket = socket;
stream->pending = NULL; stream->pending = NULL;

View File

@ -14,7 +14,7 @@
#define STREAM_MAX_SINKS 2 #define STREAM_MAX_SINKS 2
struct stream { struct stream {
sc_socket socket; socket_t socket;
sc_thread thread; sc_thread thread;
struct sc_packet_sink *sinks[STREAM_MAX_SINKS]; struct sc_packet_sink *sinks[STREAM_MAX_SINKS];
@ -35,7 +35,7 @@ struct stream_callbacks {
}; };
void void
stream_init(struct stream *stream, sc_socket socket, stream_init(struct stream *stream, socket_t socket,
const struct stream_callbacks *cbs, void *cbs_userdata); const struct stream_callbacks *cbs, void *cbs_userdata);
void void

View File

@ -1,75 +0,0 @@
#include "util/file.h"
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
bool
sc_file_executable_exists(const char *file) {
char *path = getenv("PATH");
if (!path)
return false;
path = strdup(path);
if (!path)
return false;
bool ret = false;
size_t file_len = strlen(file);
char *saveptr;
for (char *dir = strtok_r(path, ":", &saveptr); dir;
dir = strtok_r(NULL, ":", &saveptr)) {
size_t dir_len = strlen(dir);
char *fullpath = malloc(dir_len + file_len + 2);
if (!fullpath)
continue;
memcpy(fullpath, dir, dir_len);
fullpath[dir_len] = '/';
memcpy(fullpath + dir_len + 1, file, file_len + 1);
struct stat sb;
bool fullpath_executable = stat(fullpath, &sb) == 0 &&
sb.st_mode & S_IXUSR;
free(fullpath);
if (fullpath_executable) {
ret = true;
break;
}
}
free(path);
return ret;
}
char *
sc_file_get_executable_path(void) {
// <https://stackoverflow.com/a/1024937/1987178>
#ifdef __linux__
char buf[PATH_MAX + 1]; // +1 for the null byte
ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX);
if (len == -1) {
perror("readlink");
return NULL;
}
buf[len] = '\0';
return strdup(buf);
#else
// in practice, we only need this feature for portable builds, only used on
// Windows, so we don't care implementing it for every platform
// (it's useful to have a working version on Linux for debugging though)
return NULL;
#endif
}
bool
sc_file_is_regular(const char *path) {
struct stat path_stat;
if (stat(path, &path_stat)) {
perror("stat");
return false;
}
return S_ISREG(path_stat.st_mode);
}

View File

@ -3,24 +3,56 @@
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <limits.h>
#include <signal.h> #include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#include "util/log.h" #include "util/log.h"
enum sc_process_result bool
sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned inherit, search_executable(const char *file) {
int *pin, int *pout, int *perr) { char *path = getenv("PATH");
bool inherit_stdout = inherit & SC_STDOUT; if (!path)
bool inherit_stderr = inherit & SC_STDERR; return false;
path = strdup(path);
if (!path)
return false;
// If pout is defined, then inherit MUST NOT contain SC_STDOUT. bool ret = false;
assert(!pout || !inherit_stdout); size_t file_len = strlen(file);
// If perr is defined, then inherit MUST NOT contain SC_STDERR. char *saveptr;
assert(!perr || !inherit_stderr); for (char *dir = strtok_r(path, ":", &saveptr); dir;
dir = strtok_r(NULL, ":", &saveptr)) {
size_t dir_len = strlen(dir);
char *fullpath = malloc(dir_len + file_len + 2);
if (!fullpath)
continue;
memcpy(fullpath, dir, dir_len);
fullpath[dir_len] = '/';
memcpy(fullpath + dir_len + 1, file, file_len + 1);
struct stat sb;
bool fullpath_executable = stat(fullpath, &sb) == 0 &&
sb.st_mode & S_IXUSR;
free(fullpath);
if (fullpath_executable) {
ret = true;
break;
}
}
free(path);
return ret;
}
enum process_result
process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
int *pipe_stdout, int *pipe_stderr) {
int in[2]; int in[2];
int out[2]; int out[2];
int err[2]; int err[2];
@ -28,44 +60,44 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned inherit,
if (pipe(internal) == -1) { if (pipe(internal) == -1) {
perror("pipe"); perror("pipe");
return SC_PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
if (pin) { if (pipe_stdin) {
if (pipe(in) == -1) { if (pipe(in) == -1) {
perror("pipe"); perror("pipe");
close(internal[0]); close(internal[0]);
close(internal[1]); close(internal[1]);
return SC_PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
} }
if (pout) { if (pipe_stdout) {
if (pipe(out) == -1) { if (pipe(out) == -1) {
perror("pipe"); perror("pipe");
// clean up // clean up
if (pin) { if (pipe_stdin) {
close(in[0]); close(in[0]);
close(in[1]); close(in[1]);
} }
close(internal[0]); close(internal[0]);
close(internal[1]); close(internal[1]);
return SC_PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
} }
if (perr) { if (pipe_stderr) {
if (pipe(err) == -1) { if (pipe(err) == -1) {
perror("pipe"); perror("pipe");
// clean up // clean up
if (pout) { if (pipe_stdout) {
close(out[0]); close(out[0]);
close(out[1]); close(out[1]);
} }
if (pin) { if (pipe_stdin) {
close(in[0]); close(in[0]);
close(in[1]); close(in[1]);
} }
close(internal[0]); close(internal[0]);
close(internal[1]); close(internal[1]);
return SC_PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
} }
@ -73,66 +105,55 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned inherit,
if (*pid == -1) { if (*pid == -1) {
perror("fork"); perror("fork");
// clean up // clean up
if (perr) { if (pipe_stderr) {
close(err[0]); close(err[0]);
close(err[1]); close(err[1]);
} }
if (pout) { if (pipe_stdout) {
close(out[0]); close(out[0]);
close(out[1]); close(out[1]);
} }
if (pin) { if (pipe_stdin) {
close(in[0]); close(in[0]);
close(in[1]); close(in[1]);
} }
close(internal[0]); close(internal[0]);
close(internal[1]); close(internal[1]);
return SC_PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
if (*pid == 0) { if (*pid == 0) {
if (pin) { if (pipe_stdin) {
if (in[0] != STDIN_FILENO) { if (in[0] != STDIN_FILENO) {
dup2(in[0], STDIN_FILENO); dup2(in[0], STDIN_FILENO);
close(in[0]); close(in[0]);
} }
close(in[1]); close(in[1]);
} }
// Do not close stdin in the child process, this makes adb fail on if (pipe_stdout) {
// Linux
if (pout) {
if (out[1] != STDOUT_FILENO) { if (out[1] != STDOUT_FILENO) {
dup2(out[1], STDOUT_FILENO); dup2(out[1], STDOUT_FILENO);
close(out[1]); close(out[1]);
} }
close(out[0]); close(out[0]);
} else if (!inherit_stdout) {
// Close stdout in the child process
close(STDOUT_FILENO);
} }
if (pipe_stderr) {
if (perr) {
if (err[1] != STDERR_FILENO) { if (err[1] != STDERR_FILENO) {
dup2(err[1], STDERR_FILENO); dup2(err[1], STDERR_FILENO);
close(err[1]); close(err[1]);
} }
close(err[0]); close(err[0]);
} else if (!inherit_stderr) {
// Close stderr in the child process
close(STDERR_FILENO);
} }
close(internal[0]); close(internal[0]);
enum sc_process_result err; enum process_result err;
if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) { if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) {
execvp(argv[0], (char *const *) argv); execvp(argv[0], (char *const *) argv);
perror("exec"); perror("exec");
err = errno == ENOENT ? SC_PROCESS_ERROR_MISSING_BINARY err = errno == ENOENT ? PROCESS_ERROR_MISSING_BINARY
: SC_PROCESS_ERROR_GENERIC; : PROCESS_ERROR_GENERIC;
} else { } else {
perror("fcntl"); perror("fcntl");
err = SC_PROCESS_ERROR_GENERIC; err = PROCESS_ERROR_GENERIC;
} }
// send err to the parent // send err to the parent
if (write(internal[1], &err, sizeof(err)) == -1) { if (write(internal[1], &err, sizeof(err)) == -1) {
@ -147,33 +168,38 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned inherit,
close(internal[1]); close(internal[1]);
enum sc_process_result res = SC_PROCESS_SUCCESS; enum process_result res = PROCESS_SUCCESS;
// wait for EOF or receive err from child // wait for EOF or receive err from child
if (read(internal[0], &res, sizeof(res)) == -1) { if (read(internal[0], &res, sizeof(res)) == -1) {
perror("read"); perror("read");
res = SC_PROCESS_ERROR_GENERIC; res = PROCESS_ERROR_GENERIC;
} }
close(internal[0]); close(internal[0]);
if (pin) { if (pipe_stdin) {
close(in[0]); close(in[0]);
*pin = in[1]; *pipe_stdin = in[1];
} }
if (pout) { if (pipe_stdout) {
*pout = out[0]; *pipe_stdout = out[0];
close(out[1]); close(out[1]);
} }
if (perr) { if (pipe_stderr) {
*perr = err[0]; *pipe_stderr = err[0];
close(err[1]); close(err[1]);
} }
return res; return res;
} }
enum process_result
process_execute(const char *const argv[], pid_t *pid) {
return process_execute_redirect(argv, pid, NULL, NULL, NULL);
}
bool bool
sc_process_terminate(pid_t pid) { process_terminate(pid_t pid) {
if (pid <= 0) { if (pid <= 0) {
LOGC("Requested to kill %d, this is an error. Please report the bug.\n", LOGC("Requested to kill %d, this is an error. Please report the bug.\n",
(int) pid); (int) pid);
@ -182,8 +208,8 @@ sc_process_terminate(pid_t pid) {
return kill(pid, SIGKILL) != -1; return kill(pid, SIGKILL) != -1;
} }
sc_exit_code exit_code_t
sc_process_wait(pid_t pid, bool close) { process_wait(pid_t pid, bool close) {
int code; int code;
int options = WEXITED; int options = WEXITED;
if (!close) { if (!close) {
@ -194,7 +220,7 @@ sc_process_wait(pid_t pid, bool close) {
int r = waitid(P_PID, pid, &info, options); int r = waitid(P_PID, pid, &info, options);
if (r == -1 || info.si_code != CLD_EXITED) { if (r == -1 || info.si_code != CLD_EXITED) {
// could not wait, or exited unexpectedly, probably by a signal // could not wait, or exited unexpectedly, probably by a signal
code = SC_EXIT_CODE_NONE; code = NO_EXIT_CODE;
} else { } else {
code = info.si_status; code = info.si_status;
} }
@ -202,17 +228,48 @@ sc_process_wait(pid_t pid, bool close) {
} }
void void
sc_process_close(pid_t pid) { process_close(pid_t pid) {
sc_process_wait(pid, true); // ignore exit code process_wait(pid, true); // ignore exit code
}
char *
get_executable_path(void) {
// <https://stackoverflow.com/a/1024937/1987178>
#ifdef __linux__
char buf[PATH_MAX + 1]; // +1 for the null byte
ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX);
if (len == -1) {
perror("readlink");
return NULL;
}
buf[len] = '\0';
return strdup(buf);
#else
// in practice, we only need this feature for portable builds, only used on
// Windows, so we don't care implementing it for every platform
// (it's useful to have a working version on Linux for debugging though)
return NULL;
#endif
}
bool
is_regular_file(const char *path) {
struct stat path_stat;
if (stat(path, &path_stat)) {
perror("stat");
return false;
}
return S_ISREG(path_stat.st_mode);
} }
ssize_t ssize_t
sc_pipe_read(int pipe, char *data, size_t len) { read_pipe(int pipe, char *data, size_t len) {
return read(pipe, data, len); return read(pipe, data, len);
} }
void void
sc_pipe_close(int pipe) { close_pipe(int pipe) {
if (close(pipe)) { if (close(pipe)) {
perror("close pipe"); perror("close pipe");
} }

View File

@ -1,43 +0,0 @@
#include "util/file.h"
#include <windows.h>
#include <sys/stat.h>
#include "util/log.h"
#include "util/str.h"
char *
sc_file_get_executable_path(void) {
HMODULE hModule = GetModuleHandleW(NULL);
if (!hModule) {
return NULL;
}
WCHAR buf[MAX_PATH + 1]; // +1 for the null byte
int len = GetModuleFileNameW(hModule, buf, MAX_PATH);
if (!len) {
return NULL;
}
buf[len] = '\0';
return sc_str_from_wchars(buf);
}
bool
sc_file_is_regular(const char *path) {
wchar_t *wide_path = sc_str_to_wchars(path);
if (!wide_path) {
LOGC("Could not allocate wide char string");
return false;
}
struct _stat path_stat;
int r = _wstat(wide_path, &path_stat);
free(wide_path);
if (r) {
perror("stat");
return false;
}
return S_ISREG(path_stat.st_mode);
}

View File

@ -1,14 +1,10 @@
// <https://devblogs.microsoft.com/oldnewthing/20111216-00/?p=8873>
#define _WIN32_WINNT 0x0600 // For extended process API
#include "util/process.h" #include "util/process.h"
#include <processthreadsapi.h>
#include <assert.h> #include <assert.h>
#include <sys/stat.h>
#include "util/log.h" #include "util/log.h"
#include "util/str.h" #include "util/str_util.h"
#define CMD_MAX_LEN 8192 #define CMD_MAX_LEN 8192
@ -18,31 +14,19 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) {
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS> // <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
// only make it work for this very specific program // only make it work for this very specific program
// (don't handle escaping nor quotes) // (don't handle escaping nor quotes)
size_t ret = sc_str_join(cmd, argv, ' ', len); size_t ret = xstrjoin(cmd, argv, ' ', len);
if (ret >= len) { if (ret >= len) {
LOGE("Command too long (%" SC_PRIsizet " chars)", len - 1); LOGE("Command too long (%" PRIsizet " chars)", len - 1);
return false; return false;
} }
return true; return true;
} }
enum sc_process_result enum process_result
sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned inherit, process_execute_redirect(const char *const argv[], HANDLE *handle,
HANDLE *pin, HANDLE *pout, HANDLE *perr) { HANDLE *pipe_stdin, HANDLE *pipe_stdout,
bool inherit_stdout = inherit & SC_STDOUT; HANDLE *pipe_stderr) {
bool inherit_stderr = inherit & SC_STDERR; enum process_result ret = PROCESS_ERROR_GENERIC;
// If pout is defined, then inherit MUST NOT contain SC_STDOUT.
assert(!pout || !inherit_stdout);
// If perr is defined, then inherit MUST NOT contain SC_STDERR.
assert(!perr || !inherit_stderr);
// Add 1 per non-NULL pointer
unsigned handle_count = !!pin
+ (pout || inherit_stdout)
+ (perr || inherit_stderr);
enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC;
SECURITY_ATTRIBUTES sa; SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.nLength = sizeof(SECURITY_ATTRIBUTES);
@ -52,176 +36,130 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned inherit,
HANDLE stdin_read_handle; HANDLE stdin_read_handle;
HANDLE stdout_write_handle; HANDLE stdout_write_handle;
HANDLE stderr_write_handle; HANDLE stderr_write_handle;
if (pin) { if (pipe_stdin) {
if (!CreatePipe(&stdin_read_handle, pin, &sa, 0)) { if (!CreatePipe(&stdin_read_handle, pipe_stdin, &sa, 0)) {
perror("pipe"); perror("pipe");
return SC_PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
if (!SetHandleInformation(*pin, HANDLE_FLAG_INHERIT, 0)) { if (!SetHandleInformation(*pipe_stdin, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stdin failed"); LOGE("SetHandleInformation stdin failed");
goto error_close_stdin; goto error_close_stdin;
} }
} }
if (pout) { if (pipe_stdout) {
if (!CreatePipe(pout, &stdout_write_handle, &sa, 0)) { if (!CreatePipe(pipe_stdout, &stdout_write_handle, &sa, 0)) {
perror("pipe"); perror("pipe");
goto error_close_stdin; goto error_close_stdin;
} }
if (!SetHandleInformation(*pout, HANDLE_FLAG_INHERIT, 0)) { if (!SetHandleInformation(*pipe_stdout, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stdout failed"); LOGE("SetHandleInformation stdout failed");
goto error_close_stdout; goto error_close_stdout;
} }
} }
if (perr) { if (pipe_stderr) {
if (!CreatePipe(perr, &stderr_write_handle, &sa, 0)) { if (!CreatePipe(pipe_stderr, &stderr_write_handle, &sa, 0)) {
perror("pipe"); perror("pipe");
goto error_close_stdout; goto error_close_stdout;
} }
if (!SetHandleInformation(*perr, HANDLE_FLAG_INHERIT, 0)) { if (!SetHandleInformation(*pipe_stderr, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stderr failed"); LOGE("SetHandleInformation stderr failed");
goto error_close_stderr; goto error_close_stderr;
} }
} }
STARTUPINFOEXW si; STARTUPINFOW si;
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si)); memset(&si, 0, sizeof(si));
si.StartupInfo.cb = sizeof(si); si.cb = sizeof(si);
HANDLE handles[3]; if (pipe_stdin || pipe_stdout || pipe_stderr) {
si.dwFlags = STARTF_USESTDHANDLES;
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL; if (pipe_stdin) {
// Must be set even if handle_count == 0, so that stdin, stdout and stderr si.hStdInput = stdin_read_handle;
// are NOT inherited in that case
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
if (handle_count) {
unsigned i = 0;
if (pin) {
si.StartupInfo.hStdInput = stdin_read_handle;
handles[i++] = si.StartupInfo.hStdInput;
} }
if (pout || inherit_stdout) { if (pipe_stdout) {
si.StartupInfo.hStdOutput = pout ? stdout_write_handle si.hStdOutput = stdout_write_handle;
: GetStdHandle(STD_OUTPUT_HANDLE);
handles[i++] = si.StartupInfo.hStdOutput;
} }
if (perr || inherit_stderr) { if (pipe_stderr) {
si.StartupInfo.hStdError = perr ? stderr_write_handle si.hStdError = stderr_write_handle;
: GetStdHandle(STD_ERROR_HANDLE);
handles[i++] = si.StartupInfo.hStdError;
} }
SIZE_T size;
// Call it once to know the required buffer size
BOOL ok =
InitializeProcThreadAttributeList(NULL, 1, 0, &size)
|| GetLastError() == ERROR_INSUFFICIENT_BUFFER;
if (!ok) {
goto error_close_stderr;
}
lpAttributeList = malloc(size);
if (!lpAttributeList) {
goto error_close_stderr;
}
ok = InitializeProcThreadAttributeList(lpAttributeList, 1, 0, &size);
if (!ok) {
free(lpAttributeList);
goto error_close_stderr;
}
ok = UpdateProcThreadAttribute(lpAttributeList, 0,
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
handles, handle_count * sizeof(HANDLE),
NULL, NULL);
if (!ok) {
goto error_free_attribute_list;
}
si.lpAttributeList = lpAttributeList;
} }
char *cmd = malloc(CMD_MAX_LEN); char *cmd = malloc(CMD_MAX_LEN);
if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) { if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) {
goto error_free_attribute_list; *handle = NULL;
goto error_close_stderr;
} }
wchar_t *wide = sc_str_to_wchars(cmd); wchar_t *wide = utf8_to_wide_char(cmd);
free(cmd); free(cmd);
if (!wide) { if (!wide) {
LOGC("Could not allocate wide char string"); LOGC("Could not allocate wide char string");
goto error_free_attribute_list; goto error_close_stderr;
} }
BOOL bInheritHandles = handle_count > 0; if (!CreateProcessW(NULL, wide, NULL, NULL, TRUE, 0, NULL, NULL, &si,
DWORD dwCreationFlags = handle_count > 0 ? EXTENDED_STARTUPINFO_PRESENT : 0; &pi)) {
BOOL ok = CreateProcessW(NULL, wide, NULL, NULL, bInheritHandles,
dwCreationFlags, NULL, NULL, &si.StartupInfo, &pi);
free(wide); free(wide);
if (!ok) { *handle = NULL;
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
ret = SC_PROCESS_ERROR_MISSING_BINARY;
}
goto error_free_attribute_list;
}
if (lpAttributeList) { if (GetLastError() == ERROR_FILE_NOT_FOUND) {
DeleteProcThreadAttributeList(lpAttributeList); ret = PROCESS_ERROR_MISSING_BINARY;
free(lpAttributeList); }
goto error_close_stderr;
} }
// These handles are used by the child process, close them for this process // These handles are used by the child process, close them for this process
if (pin) { if (pipe_stdin) {
CloseHandle(stdin_read_handle); CloseHandle(stdin_read_handle);
} }
if (pout) { if (pipe_stdout) {
CloseHandle(stdout_write_handle); CloseHandle(stdout_write_handle);
} }
if (perr) { if (pipe_stderr) {
CloseHandle(stderr_write_handle); CloseHandle(stderr_write_handle);
} }
free(wide);
*handle = pi.hProcess; *handle = pi.hProcess;
return SC_PROCESS_SUCCESS; return PROCESS_SUCCESS;
error_free_attribute_list:
if (lpAttributeList) {
DeleteProcThreadAttributeList(lpAttributeList);
free(lpAttributeList);
}
error_close_stderr: error_close_stderr:
if (perr) { if (pipe_stderr) {
CloseHandle(*perr); CloseHandle(*pipe_stderr);
CloseHandle(stderr_write_handle); CloseHandle(stderr_write_handle);
} }
error_close_stdout: error_close_stdout:
if (pout) { if (pipe_stdout) {
CloseHandle(*pout); CloseHandle(*pipe_stdout);
CloseHandle(stdout_write_handle); CloseHandle(stdout_write_handle);
} }
error_close_stdin: error_close_stdin:
if (pin) { if (pipe_stdin) {
CloseHandle(*pin); CloseHandle(*pipe_stdin);
CloseHandle(stdin_read_handle); CloseHandle(stdin_read_handle);
} }
return ret; return ret;
} }
enum process_result
process_execute(const char *const argv[], HANDLE *handle) {
return process_execute_redirect(argv, handle, NULL, NULL, NULL);
}
bool bool
sc_process_terminate(HANDLE handle) { process_terminate(HANDLE handle) {
return TerminateProcess(handle, 1); return TerminateProcess(handle, 1);
} }
sc_exit_code exit_code_t
sc_process_wait(HANDLE handle, bool close) { process_wait(HANDLE handle, bool close) {
DWORD code; DWORD code;
if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0
|| !GetExitCodeProcess(handle, &code)) { || !GetExitCodeProcess(handle, &code)) {
// could not wait or retrieve the exit code // could not wait or retrieve the exit code
code = SC_EXIT_CODE_NONE; code = NO_EXIT_CODE; // max value, it's unsigned
} }
if (close) { if (close) {
CloseHandle(handle); CloseHandle(handle);
@ -230,14 +168,48 @@ sc_process_wait(HANDLE handle, bool close) {
} }
void void
sc_process_close(HANDLE handle) { process_close(HANDLE handle) {
bool closed = CloseHandle(handle); bool closed = CloseHandle(handle);
assert(closed); assert(closed);
(void) closed; (void) closed;
} }
char *
get_executable_path(void) {
HMODULE hModule = GetModuleHandleW(NULL);
if (!hModule) {
return NULL;
}
WCHAR buf[MAX_PATH + 1]; // +1 for the null byte
int len = GetModuleFileNameW(hModule, buf, MAX_PATH);
if (!len) {
return NULL;
}
buf[len] = '\0';
return utf8_from_wide_char(buf);
}
bool
is_regular_file(const char *path) {
wchar_t *wide_path = utf8_to_wide_char(path);
if (!wide_path) {
LOGC("Could not allocate wide char string");
return false;
}
struct _stat path_stat;
int r = _wstat(wide_path, &path_stat);
free(wide_path);
if (r) {
perror("stat");
return false;
}
return S_ISREG(path_stat.st_mode);
}
ssize_t ssize_t
sc_pipe_read(HANDLE pipe, char *data, size_t len) { read_pipe(HANDLE pipe, char *data, size_t len) {
DWORD r; DWORD r;
if (!ReadFile(pipe, data, len, &r, NULL)) { if (!ReadFile(pipe, data, len, &r, NULL)) {
return -1; return -1;
@ -246,7 +218,7 @@ sc_pipe_read(HANDLE pipe, char *data, size_t len) {
} }
void void
sc_pipe_close(HANDLE pipe) { close_pipe(HANDLE pipe) {
if (!CloseHandle(pipe)) { if (!CloseHandle(pipe)) {
LOGW("Cannot close pipe"); LOGW("Cannot close pipe");
} }

119
app/src/tiny_xpm.c Normal file
View File

@ -0,0 +1,119 @@
#include "tiny_xpm.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "util/log.h"
struct index {
char c;
uint32_t color;
};
static bool
find_color(struct index *index, int len, char c, uint32_t *color) {
// there are typically very few color, so it's ok to iterate over the array
for (int i = 0; i < len; ++i) {
if (index[i].c == c) {
*color = index[i].color;
return true;
}
}
*color = 0;
return false;
}
// We encounter some problems with SDL2_image on MSYS2 (Windows),
// so here is our own XPM parsing not to depend on SDL_image.
//
// We do not hardcode the binary image to keep some flexibility to replace the
// icon easily (just by replacing icon.xpm).
//
// Parameter is not "const char *" because XPM formats are generally stored in a
// (non-const) "char *"
SDL_Surface *
read_xpm(char *xpm[]) {
#ifndef NDEBUG
// patch the XPM to change the icon color in debug mode
xpm[2] = ". c #CC00CC";
#endif
char *endptr;
// *** No error handling, assume the XPM source is valid ***
// (it's in our source repo)
// Assertions are only checked in debug
int width = strtol(xpm[0], &endptr, 10);
int height = strtol(endptr + 1, &endptr, 10);
int colors = strtol(endptr + 1, &endptr, 10);
int chars = strtol(endptr + 1, &endptr, 10);
// sanity checks
assert(0 <= width && width < 256);
assert(0 <= height && height < 256);
assert(0 <= colors && colors < 256);
assert(chars == 1); // this implementation does not support more
(void) chars;
// init index
struct index index[colors];
for (int i = 0; i < colors; ++i) {
const char *line = xpm[1+i];
index[i].c = line[0];
assert(line[1] == '\t');
assert(line[2] == 'c');
assert(line[3] == ' ');
if (line[4] == '#') {
index[i].color = 0xff000000 | strtol(&line[5], &endptr, 0x10);
assert(*endptr == '\0');
} else {
assert(!strcmp("None", &line[4]));
index[i].color = 0;
}
}
// parse image
uint32_t *pixels = SDL_malloc(4 * width * height);
if (!pixels) {
LOGE("Could not allocate icon memory");
return NULL;
}
for (int y = 0; y < height; ++y) {
const char *line = xpm[1 + colors + y];
for (int x = 0; x < width; ++x) {
char c = line[x];
uint32_t color;
bool color_found = find_color(index, colors, c, &color);
assert(color_found);
(void) color_found;
pixels[y * width + x] = color;
}
}
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
uint32_t amask = 0x000000ff;
uint32_t rmask = 0x0000ff00;
uint32_t gmask = 0x00ff0000;
uint32_t bmask = 0xff000000;
#else // little endian, like x86
uint32_t amask = 0xff000000;
uint32_t rmask = 0x00ff0000;
uint32_t gmask = 0x0000ff00;
uint32_t bmask = 0x000000ff;
#endif
SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(pixels,
width, height,
32, 4 * width,
rmask, gmask, bmask, amask);
if (!surface) {
LOGE("Could not create icon surface");
return NULL;
}
// make the surface own the raw pixels
surface->flags &= ~SDL_PREALLOC;
return surface;
}

11
app/src/tiny_xpm.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef TINYXPM_H
#define TINYXPM_H
#include "common.h"
#include <SDL2/SDL.h>
SDL_Surface *
read_xpm(char *xpm[]);
#endif

View File

@ -1,48 +0,0 @@
#include "file.h"
#include <stdlib.h>
#include <string.h>
#include "util/log.h"
char *
sc_file_get_local_path(const char *name) {
char *executable_path = sc_file_get_executable_path();
if (!executable_path) {
return NULL;
}
// dirname() does not work correctly everywhere, so get the parent
// directory manually.
// See <https://github.com/Genymobile/scrcpy/issues/2619>
char *p = strrchr(executable_path, SC_PATH_SEPARATOR);
if (!p) {
LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')",
executable_path, SC_PATH_SEPARATOR);
free(executable_path);
return NULL;
}
*p = '\0'; // modify executable_path in place
char *dir = executable_path;
size_t dirlen = strlen(dir);
size_t namelen = strlen(name);
size_t len = dirlen + namelen + 2; // +2: '/' and '\0'
char *file_path = malloc(len);
if (!file_path) {
LOGE("Could not alloc path");
free(executable_path);
return NULL;
}
memcpy(file_path, dir, dirlen);
file_path[dirlen] = SC_PATH_SEPARATOR;
// namelen + 1 to copy the final '\0'
memcpy(&file_path[dirlen + 1], name, namelen + 1);
free(executable_path);
return file_path;
}

View File

@ -1,49 +0,0 @@
#ifndef SC_FILE_H
#define SC_FILE_H
#include "common.h"
#include <stdbool.h>
#ifdef _WIN32
# define SC_PATH_SEPARATOR '\\'
#else
# define SC_PATH_SEPARATOR '/'
#endif
#ifndef _WIN32
/**
* Indicate if an executable exists using $PATH
*
* In practice, it is only used to know if a package manager is available on
* the system. It is only implemented on Linux.
*/
bool
sc_file_executable_exists(const char *file);
#endif
/**
* Return the absolute path of the executable (the scrcpy binary)
*
* The result must be freed by the caller using free(). It may return NULL on
* error.
*/
char *
sc_file_get_executable_path(void);
/**
* Return the absolute path of a file in the same directory as the executable
*
* The result must be freed by the caller using free(). It may return NULL on
* error.
*/
char *
sc_file_get_local_path(const char *name);
/**
* Indicate if the file exists and is not a directory
*/
bool
sc_file_is_regular(const char *path);
#endif

View File

@ -1,83 +0,0 @@
#include "intr.h"
#include "util/log.h"
#include <assert.h>
bool
sc_intr_init(struct sc_intr *intr) {
bool ok = sc_mutex_init(&intr->mutex);
if (!ok) {
LOGE("Could not init intr mutex");
return false;
}
intr->socket = SC_SOCKET_NONE;
intr->process = SC_PROCESS_NONE;
atomic_store_explicit(&intr->interrupted, false, memory_order_relaxed);
return true;
}
bool
sc_intr_set_socket(struct sc_intr *intr, sc_socket socket) {
assert(intr->process == SC_PROCESS_NONE);
sc_mutex_lock(&intr->mutex);
bool interrupted =
atomic_load_explicit(&intr->interrupted, memory_order_relaxed);
if (!interrupted) {
intr->socket = socket;
}
sc_mutex_unlock(&intr->mutex);
return !interrupted;
}
bool
sc_intr_set_process(struct sc_intr *intr, sc_pid pid) {
assert(intr->socket == SC_SOCKET_NONE);
sc_mutex_lock(&intr->mutex);
bool interrupted =
atomic_load_explicit(&intr->interrupted, memory_order_relaxed);
if (!interrupted) {
intr->process = pid;
}
sc_mutex_unlock(&intr->mutex);
return !interrupted;
}
void
sc_intr_interrupt(struct sc_intr *intr) {
sc_mutex_lock(&intr->mutex);
atomic_store_explicit(&intr->interrupted, true, memory_order_relaxed);
// No more than one component to interrupt
assert(intr->socket == SC_SOCKET_NONE ||
intr->process == SC_PROCESS_NONE);
if (intr->socket != SC_SOCKET_NONE) {
LOGD("Interrupting socket");
net_interrupt(intr->socket);
intr->socket = SC_SOCKET_NONE;
}
if (intr->process != SC_PROCESS_NONE) {
LOGD("Interrupting process");
sc_process_terminate(intr->process);
intr->process = SC_PROCESS_NONE;
}
sc_mutex_unlock(&intr->mutex);
}
void
sc_intr_destroy(struct sc_intr *intr) {
assert(intr->socket == SC_SOCKET_NONE);
assert(intr->process == SC_PROCESS_NONE);
sc_mutex_destroy(&intr->mutex);
}

View File

@ -1,78 +0,0 @@
#ifndef SC_INTR_H
#define SC_INTR_H
#include "common.h"
#include <stdatomic.h>
#include <stdbool.h>
#include "net.h"
#include "process.h"
#include "thread.h"
/**
* Interruptor to wake up a blocking call from another thread
*
* It allows to register a socket or a process before a blocking call, and
* interrupt/close from another thread to wake up the blocking call.
*/
struct sc_intr {
sc_mutex mutex;
sc_socket socket;
sc_pid process;
// Written protected by the mutex to avoid race conditions against
// sc_intr_set_socket() and sc_intr_set_process(), but can be read
// (atomically) without mutex
atomic_bool interrupted;
};
/**
* Initialize an interruptor
*/
bool
sc_intr_init(struct sc_intr *intr);
/**
* Set a socket as the interruptible component
*
* Call with SC_SOCKET_NONE to unset.
*/
bool
sc_intr_set_socket(struct sc_intr *intr, sc_socket socket);
/**
* Set a process as the interruptible component
*
* Call with SC_PROCESS_NONE to unset.
*/
bool
sc_intr_set_process(struct sc_intr *intr, sc_pid socket);
/**
* Interrupt the current interruptible component
*
* Must be called from a different thread.
*/
void
sc_intr_interrupt(struct sc_intr *intr);
/**
* Read the interrupted state
*
* It is exposed as a static inline function because it just loads from an
* atomic.
*/
static inline bool
sc_intr_is_interrupted(struct sc_intr *intr) {
return atomic_load_explicit(&intr->interrupted, memory_order_relaxed);
}
/**
* Destroy the interruptor
*/
void
sc_intr_destroy(struct sc_intr *intr);
#endif

View File

@ -5,7 +5,7 @@
#include <SDL2/SDL_log.h> #include <SDL2/SDL_log.h>
#include "options.h" #include "scrcpy.h"
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__) #define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)

View File

@ -1,6 +1,5 @@
#include "net.h" #include "net.h"
#include <assert.h>
#include <stdio.h> #include <stdio.h>
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
@ -8,7 +7,6 @@
#ifdef __WINDOWS__ #ifdef __WINDOWS__
typedef int socklen_t; typedef int socklen_t;
typedef SOCKET sc_raw_socket;
#else #else
# include <sys/types.h> # include <sys/types.h>
# include <sys/socket.h> # include <sys/socket.h>
@ -19,9 +17,122 @@
typedef struct sockaddr_in SOCKADDR_IN; typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR; typedef struct sockaddr SOCKADDR;
typedef struct in_addr IN_ADDR; typedef struct in_addr IN_ADDR;
typedef int sc_raw_socket;
#endif #endif
static void
net_perror(const char *s) {
#ifdef _WIN32
int error = WSAGetLastError();
char *wsa_message;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(char *) &wsa_message, 0, NULL);
// no explicit '\n', wsa_message already contains a trailing '\n'
fprintf(stderr, "%s: [%d] %s", s, error, wsa_message);
LocalFree(wsa_message);
#else
perror(s);
#endif
}
socket_t
net_connect(uint32_t addr, uint16_t port) {
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
net_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) {
net_perror("connect");
net_close(sock);
return INVALID_SOCKET;
}
return sock;
}
socket_t
net_listen(uint32_t addr, uint16_t port, int backlog) {
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
net_perror("socket");
return INVALID_SOCKET;
}
int reuse = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
sizeof(reuse)) == -1) {
net_perror("setsockopt(SO_REUSEADDR)");
}
SOCKADDR_IN sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(addr); // htonl() harmless on INADDR_ANY
sin.sin_port = htons(port);
if (bind(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
net_perror("bind");
net_close(sock);
return INVALID_SOCKET;
}
if (listen(sock, backlog) == SOCKET_ERROR) {
net_perror("listen");
net_close(sock);
return INVALID_SOCKET;
}
return sock;
}
socket_t
net_accept(socket_t server_socket) {
SOCKADDR_IN csin;
socklen_t sinsize = sizeof(csin);
return accept(server_socket, (SOCKADDR *) &csin, &sinsize);
}
ssize_t
net_recv(socket_t socket, void *buf, size_t len) {
return recv(socket, buf, len, 0);
}
ssize_t
net_recv_all(socket_t socket, void *buf, size_t len) {
return recv(socket, buf, len, MSG_WAITALL);
}
ssize_t
net_send(socket_t socket, const void *buf, size_t len) {
return send(socket, buf, len, 0);
}
ssize_t
net_send_all(socket_t socket, const void *buf, size_t len) {
size_t copied = 0;
while (len > 0) {
ssize_t w = send(socket, buf, len, 0);
if (w == -1) {
return copied ? (ssize_t) copied : -1;
}
len -= w;
buf = (char *) buf + w;
copied += w;
}
return copied;
}
bool
net_shutdown(socket_t socket, int how) {
return !shutdown(socket, how);
}
bool bool
net_init(void) { net_init(void) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
@ -42,186 +153,11 @@ net_cleanup(void) {
#endif #endif
} }
static inline sc_socket
wrap(sc_raw_socket sock) {
#ifdef __WINDOWS__
if (sock == INVALID_SOCKET) {
return SC_SOCKET_NONE;
}
struct sc_socket_windows *socket = malloc(sizeof(*socket));
if (!socket) {
closesocket(sock);
return SC_SOCKET_NONE;
}
socket->socket = sock;
socket->closed = (atomic_flag) ATOMIC_FLAG_INIT;
return socket;
#else
return sock;
#endif
}
static inline sc_raw_socket
unwrap(sc_socket socket) {
#ifdef __WINDOWS__
if (socket == SC_SOCKET_NONE) {
return INVALID_SOCKET;
}
return socket->socket;
#else
return socket;
#endif
}
static void
net_perror(const char *s) {
#ifdef _WIN32
int error = WSAGetLastError();
char *wsa_message;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(char *) &wsa_message, 0, NULL);
// no explicit '\n', wsa_message already contains a trailing '\n'
fprintf(stderr, "%s: [%d] %s", s, error, wsa_message);
LocalFree(wsa_message);
#else
perror(s);
#endif
}
sc_socket
net_socket(void) {
sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM, 0);
sc_socket sock = wrap(raw_sock);
if (sock == SC_SOCKET_NONE) {
net_perror("socket");
}
return sock;
}
bool bool
net_connect(sc_socket socket, uint32_t addr, uint16_t port) { net_close(socket_t socket) {
sc_raw_socket raw_sock = unwrap(socket);
SOCKADDR_IN sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(addr);
sin.sin_port = htons(port);
if (connect(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
net_perror("connect");
return false;
}
return true;
}
bool
net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog) {
sc_raw_socket raw_sock = unwrap(socket);
int reuse = 1;
if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
sizeof(reuse)) == -1) {
net_perror("setsockopt(SO_REUSEADDR)");
}
SOCKADDR_IN sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(addr); // htonl() harmless on INADDR_ANY
sin.sin_port = htons(port);
if (bind(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
net_perror("bind");
return false;
}
if (listen(raw_sock, backlog) == SOCKET_ERROR) {
net_perror("listen");
return false;
}
return true;
}
sc_socket
net_accept(sc_socket server_socket) {
sc_raw_socket raw_server_socket = unwrap(server_socket);
SOCKADDR_IN csin;
socklen_t sinsize = sizeof(csin);
sc_raw_socket raw_sock =
accept(raw_server_socket, (SOCKADDR *) &csin, &sinsize);
return wrap(raw_sock);
}
ssize_t
net_recv(sc_socket socket, void *buf, size_t len) {
sc_raw_socket raw_sock = unwrap(socket);
return recv(raw_sock, buf, len, 0);
}
ssize_t
net_recv_all(sc_socket socket, void *buf, size_t len) {
sc_raw_socket raw_sock = unwrap(socket);
return recv(raw_sock, buf, len, MSG_WAITALL);
}
ssize_t
net_send(sc_socket socket, const void *buf, size_t len) {
sc_raw_socket raw_sock = unwrap(socket);
return send(raw_sock, buf, len, 0);
}
ssize_t
net_send_all(sc_socket socket, const void *buf, size_t len) {
size_t copied = 0;
while (len > 0) {
ssize_t w = net_send(socket, buf, len);
if (w == -1) {
return copied ? (ssize_t) copied : -1;
}
len -= w;
buf = (char *) buf + w;
copied += w;
}
return copied;
}
bool
net_interrupt(sc_socket socket) {
assert(socket != SC_SOCKET_NONE);
sc_raw_socket raw_sock = unwrap(socket);
#ifdef __WINDOWS__ #ifdef __WINDOWS__
if (!atomic_flag_test_and_set(&socket->closed)) { return !closesocket(socket);
return !closesocket(raw_sock);
}
return true;
#else #else
return !shutdown(raw_sock, SHUT_RDWR); return !close(socket);
#endif
}
#include <errno.h>
bool
net_close(sc_socket socket) {
sc_raw_socket raw_sock = unwrap(socket);
#ifdef __WINDOWS__
bool ret = true;
if (!atomic_flag_test_and_set(&socket->closed)) {
ret = !closesocket(raw_sock);
}
free(socket);
return ret;
#else
return !close(raw_sock);
#endif #endif
} }

View File

@ -8,64 +8,50 @@
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
#ifdef __WINDOWS__ #ifdef __WINDOWS__
# include <winsock2.h> # include <winsock2.h>
# include <stdatomic.h> #define SHUT_RD SD_RECEIVE
# define SC_SOCKET_NONE NULL #define SHUT_WR SD_SEND
typedef struct sc_socket_windows { #define SHUT_RDWR SD_BOTH
SOCKET socket; typedef SOCKET socket_t;
atomic_flag closed; #else
} *sc_socket;
#else // not __WINDOWS__
# include <sys/socket.h> # include <sys/socket.h>
# define SC_SOCKET_NONE -1 # define INVALID_SOCKET -1
typedef int sc_socket; typedef int socket_t;
#endif #endif
#define IPV4_LOCALHOST 0x7F000001
bool bool
net_init(void); net_init(void);
void void
net_cleanup(void); net_cleanup(void);
sc_socket socket_t
net_socket(void); net_connect(uint32_t addr, uint16_t port);
bool socket_t
net_connect(sc_socket socket, uint32_t addr, uint16_t port); net_listen(uint32_t addr, uint16_t port, int backlog);
bool socket_t
net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog); net_accept(socket_t server_socket);
sc_socket
net_accept(sc_socket server_socket);
// the _all versions wait/retry until len bytes have been written/read // the _all versions wait/retry until len bytes have been written/read
ssize_t ssize_t
net_recv(sc_socket socket, void *buf, size_t len); net_recv(socket_t socket, void *buf, size_t len);
ssize_t ssize_t
net_recv_all(sc_socket socket, void *buf, size_t len); net_recv_all(socket_t socket, void *buf, size_t len);
ssize_t ssize_t
net_send(sc_socket socket, const void *buf, size_t len); net_send(socket_t socket, const void *buf, size_t len);
ssize_t ssize_t
net_send_all(sc_socket socket, const void *buf, size_t len); net_send_all(socket_t socket, const void *buf, size_t len);
// Shutdown the socket (or close on Windows) so that any blocking send() or // how is SHUT_RD (read), SHUT_WR (write) or SHUT_RDWR (both)
// recv() are interrupted.
bool bool
net_interrupt(sc_socket socket); net_shutdown(socket_t socket, int how);
// Close the socket.
// A socket must always be closed, even if net_interrupt() has been called.
bool bool
net_close(sc_socket socket); net_close(socket_t socket);
#endif #endif

View File

@ -1,97 +0,0 @@
#include "net_intr.h"
bool
net_connect_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
uint16_t port) {
if (!sc_intr_set_socket(intr, socket)) {
// Already interrupted
return false;
}
bool ret = net_connect(socket, addr, port);
sc_intr_set_socket(intr, SC_SOCKET_NONE);
return ret;
}
bool
net_listen_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
uint16_t port, int backlog) {
if (!sc_intr_set_socket(intr, socket)) {
// Already interrupted
return false;
}
bool ret = net_listen(socket, addr, port, backlog);
sc_intr_set_socket(intr, SC_SOCKET_NONE);
return ret;
}
sc_socket
net_accept_intr(struct sc_intr *intr, sc_socket server_socket) {
if (!sc_intr_set_socket(intr, server_socket)) {
// Already interrupted
return SC_SOCKET_NONE;
}
sc_socket socket = net_accept(server_socket);
sc_intr_set_socket(intr, SC_SOCKET_NONE);
return socket;
}
ssize_t
net_recv_intr(struct sc_intr *intr, sc_socket socket, void *buf, size_t len) {
if (!sc_intr_set_socket(intr, socket)) {
// Already interrupted
return -1;
}
ssize_t r = net_recv(socket, buf, len);
sc_intr_set_socket(intr, SC_SOCKET_NONE);
return r;
}
ssize_t
net_recv_all_intr(struct sc_intr *intr, sc_socket socket, void *buf,
size_t len) {
if (!sc_intr_set_socket(intr, socket)) {
// Already interrupted
return -1;
}
ssize_t r = net_recv_all(socket, buf, len);
sc_intr_set_socket(intr, SC_SOCKET_NONE);
return r;
}
ssize_t
net_send_intr(struct sc_intr *intr, sc_socket socket, const void *buf,
size_t len) {
if (!sc_intr_set_socket(intr, socket)) {
// Already interrupted
return -1;
}
ssize_t w = net_send(socket, buf, len);
sc_intr_set_socket(intr, SC_SOCKET_NONE);
return w;
}
ssize_t
net_send_all_intr(struct sc_intr *intr, sc_socket socket, const void *buf,
size_t len) {
if (!sc_intr_set_socket(intr, socket)) {
// Already interrupted
return -1;
}
ssize_t w = net_send_all(socket, buf, len);
sc_intr_set_socket(intr, SC_SOCKET_NONE);
return w;
}

View File

@ -1,35 +0,0 @@
#ifndef SC_NET_INTR_H
#define SC_NET_INTR_H
#include "common.h"
#include "intr.h"
#include "net.h"
bool
net_connect_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
uint16_t port);
bool
net_listen_intr(struct sc_intr *intr, sc_socket socket, uint32_t addr,
uint16_t port, int backlog);
sc_socket
net_accept_intr(struct sc_intr *intr, sc_socket server_socket);
ssize_t
net_recv_intr(struct sc_intr *intr, sc_socket socket, void *buf, size_t len);
ssize_t
net_recv_all_intr(struct sc_intr *intr, sc_socket socket, void *buf,
size_t len);
ssize_t
net_send_intr(struct sc_intr *intr, sc_socket socket, const void *buf,
size_t len);
ssize_t
net_send_all_intr(struct sc_intr *intr, sc_socket socket, const void *buf,
size_t len);
#endif

View File

@ -1,25 +1,17 @@
#include "process.h" #include "process.h"
#include <assert.h>
#include <libgen.h>
#include "log.h" #include "log.h"
enum sc_process_result
sc_process_execute(const char *const argv[], sc_pid *pid, unsigned inherit) {
return sc_process_execute_p(argv, pid, inherit, NULL, NULL, NULL);
}
bool bool
sc_process_check_success(sc_pid pid, const char *name, bool close) { process_check_success(process_t proc, const char *name, bool close) {
if (pid == SC_PROCESS_NONE) { if (proc == PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name); LOGE("Could not execute \"%s\"", name);
return false; return false;
} }
sc_exit_code exit_code = sc_process_wait(pid, close); exit_code_t exit_code = process_wait(proc, close);
if (exit_code) { if (exit_code) {
if (exit_code != SC_EXIT_CODE_NONE) { if (exit_code != NO_EXIT_CODE) {
LOGE("\"%s\" returned with value %" SC_PRIexitcode, name, LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code);
exit_code);
} else { } else {
LOGE("\"%s\" exited unexpectedly", name); LOGE("\"%s\" exited unexpectedly", name);
} }
@ -29,10 +21,10 @@ sc_process_check_success(sc_pid pid, const char *name, bool close) {
} }
ssize_t ssize_t
sc_pipe_read_all(sc_pipe pipe, char *data, size_t len) { read_pipe_all(pipe_t pipe, char *data, size_t len) {
size_t copied = 0; size_t copied = 0;
while (len > 0) { while (len > 0) {
ssize_t r = sc_pipe_read(pipe, data, len); ssize_t r = read_pipe(pipe, data, len);
if (r <= 0) { if (r <= 0) {
return copied ? (ssize_t) copied : r; return copied ? (ssize_t) copied : r;
} }
@ -42,80 +34,3 @@ sc_pipe_read_all(sc_pipe pipe, char *data, size_t len) {
} }
return copied; return copied;
} }
static int
run_observer(void *data) {
struct sc_process_observer *observer = data;
sc_process_wait(observer->pid, false); // ignore exit code
sc_mutex_lock(&observer->mutex);
observer->terminated = true;
sc_cond_signal(&observer->cond_terminated);
sc_mutex_unlock(&observer->mutex);
if (observer->listener) {
observer->listener->on_terminated(observer->listener_userdata);
}
return 0;
}
bool
sc_process_observer_init(struct sc_process_observer *observer, sc_pid pid,
const struct sc_process_listener *listener,
void *listener_userdata) {
// Either no listener, or on_terminated() is defined
assert(!listener || listener->on_terminated);
bool ok = sc_mutex_init(&observer->mutex);
if (!ok) {
return false;
}
ok = sc_cond_init(&observer->cond_terminated);
if (!ok) {
sc_mutex_destroy(&observer->mutex);
return false;
}
observer->pid = pid;
observer->listener = listener;
observer->listener_userdata = listener_userdata;
observer->terminated = false;
ok = sc_thread_create(&observer->thread, run_observer, "process_observer",
observer);
if (!ok) {
sc_cond_destroy(&observer->cond_terminated);
sc_mutex_destroy(&observer->mutex);
return false;
}
return true;
}
bool
sc_process_observer_timedwait(struct sc_process_observer *observer,
sc_tick deadline) {
sc_mutex_lock(&observer->mutex);
bool timed_out = false;
while (!observer->terminated && !timed_out) {
timed_out = !sc_cond_timedwait(&observer->cond_terminated,
&observer->mutex, deadline);
}
bool terminated = observer->terminated;
sc_mutex_unlock(&observer->mutex);
return terminated;
}
void
sc_process_observer_join(struct sc_process_observer *observer) {
sc_thread_join(&observer->thread, NULL);
}
void
sc_process_observer_destroy(struct sc_process_observer *observer) {
sc_cond_destroy(&observer->cond_terminated);
sc_mutex_destroy(&observer->mutex);
}

View File

@ -4,184 +4,97 @@
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include "util/thread.h"
#ifdef _WIN32 #ifdef _WIN32
// not needed here, but winsock2.h must never be included AFTER windows.h // not needed here, but winsock2.h must never be included AFTER windows.h
# include <winsock2.h> # include <winsock2.h>
# include <windows.h> # include <windows.h>
# define SC_PRIexitcode "lu" # define PATH_SEPARATOR '\\'
# define PRIexitcode "lu"
// <https://stackoverflow.com/a/44383330/1987178> // <https://stackoverflow.com/a/44383330/1987178>
# define SC_PRIsizet "Iu" # define PRIsizet "Iu"
# define SC_PROCESS_NONE NULL # define PROCESS_NONE NULL
# define SC_EXIT_CODE_NONE -1u // max value as unsigned # define NO_EXIT_CODE -1u // max value as unsigned
typedef HANDLE sc_pid; typedef HANDLE process_t;
typedef DWORD sc_exit_code; typedef DWORD exit_code_t;
typedef HANDLE sc_pipe; typedef HANDLE pipe_t;
#else #else
# include <sys/types.h> # include <sys/types.h>
# define SC_PRIsizet "zu" # define PATH_SEPARATOR '/'
# define SC_PRIexitcode "d" # define PRIsizet "zu"
# define SC_PROCESS_NONE -1 # define PRIexitcode "d"
# define SC_EXIT_CODE_NONE -1 # define PROCESS_NONE -1
typedef pid_t sc_pid; # define NO_EXIT_CODE -1
typedef int sc_exit_code; typedef pid_t process_t;
typedef int sc_pipe; typedef int exit_code_t;
typedef int pipe_t;
#endif #endif
struct sc_process_listener { enum process_result {
void (*on_terminated)(void *userdata); PROCESS_SUCCESS,
PROCESS_ERROR_GENERIC,
PROCESS_ERROR_MISSING_BINARY,
}; };
/** // execute the command and write the result to the output parameter "process"
* Tool to observe process termination enum process_result
* process_execute(const char *const argv[], process_t *process);
* To keep things simple and multiplatform, it runs a separate thread to wait
* for process termination (without closing the process to avoid race
* conditions).
*
* It allows a caller to block until the process is terminated (with a
* timeout), and to be notified asynchronously from the observer thread.
*
* The process is not owned by the observer (the observer will never close it).
*/
struct sc_process_observer {
sc_pid pid;
sc_mutex mutex; enum process_result
sc_cond cond_terminated; process_execute_redirect(const char *const argv[], process_t *process,
bool terminated; pipe_t *pipe_stdin, pipe_t *pipe_stdout,
pipe_t *pipe_stderr);
sc_thread thread;
const struct sc_process_listener *listener;
void *listener_userdata;
};
enum sc_process_result {
SC_PROCESS_SUCCESS,
SC_PROCESS_ERROR_GENERIC,
SC_PROCESS_ERROR_MISSING_BINARY,
};
#define SC_STDOUT (1 << 0)
#define SC_STDERR (1 << 1)
/**
* Execute the command and write the process id to `pid`
*
* The parameter `inherit` is a OR of any of SC_STDOUT and SC_STDERR. It
* indicates if stdout and stderr must be inherited from the scrcpy process (in
* other words, if the process must output to the scrcpy console).
*/
enum sc_process_result
sc_process_execute(const char *const argv[], sc_pid *pid, unsigned inherit);
/**
* Execute the command and write the process id to `pid`
*
* If not NULL, provide a pipe for stdin (`pin`), stdout (`pout`) and stderr
* (`perr`).
*
* The parameter `inherit` has the same semantics as in `sc_process_execute()`.
* If `pout` is not NULL, then `inherit` MUST NOT contain SC_STDOUT.
* If `perr` is not NULL, then `inherit` MUST NOT contain SC_STDERR.
*/
enum sc_process_result
sc_process_execute_p(const char *const argv[], sc_pid *pid, unsigned inherit,
sc_pipe *pin, sc_pipe *pout, sc_pipe *perr);
/**
* Kill the process
*/
bool bool
sc_process_terminate(sc_pid pid); process_terminate(process_t pid);
/** // kill the process
* Wait and close the process (similar to waitpid()) bool
* process_terminate(process_t pid);
* The `close` flag indicates if the process must be _closed_ (reaped) (passing
* false is equivalent to enable WNOWAIT in waitid()).
*/
sc_exit_code
sc_process_wait(sc_pid pid, bool close);
/** // wait and close the process (like waitpid())
* Close (reap) the process // the "close" flag indicates if the process must be "closed" (reaped)
* // (passing false is equivalent to enable WNOWAIT in waitid())
* Semantically: exit_code_t
* sc_process_wait(close) = sc_process_wait(noclose) + sc_process_close() process_wait(process_t pid, bool close);
*/
// close the process
//
// Semantically, process_wait(close) = process_wait(noclose) + process_close
void void
sc_process_close(sc_pid pid); process_close(process_t pid);
/** // convenience function to wait for a successful process execution
* Convenience function to wait for a successful process execution // automatically log process errors with the provided process name
*
* Automatically log process errors with the provided process name.
*/
bool bool
sc_process_check_success(sc_pid pid, const char *name, bool close); process_check_success(process_t proc, const char *name, bool close);
#ifndef _WIN32
// only used to find package manager, not implemented for Windows
bool
search_executable(const char *file);
#endif
// return the absolute path of the executable (the scrcpy binary)
// may be NULL on error; to be freed by free()
char *
get_executable_path(void);
// returns true if the file exists and is not a directory
bool
is_regular_file(const char *path);
/**
* Read from the pipe
*
* Same semantic as read().
*/
ssize_t ssize_t
sc_pipe_read(sc_pipe pipe, char *data, size_t len); read_pipe(pipe_t pipe, char *data, size_t len);
/**
* Read exactly `len` chars from a pipe (unless EOF)
*/
ssize_t ssize_t
sc_pipe_read_all(sc_pipe pipe, char *data, size_t len); read_pipe_all(pipe_t pipe, char *data, size_t len);
/**
* Close the pipe
*/
void void
sc_pipe_close(sc_pipe pipe); close_pipe(pipe_t pipe);
/**
* Start observing process
*
* The listener is optional. If set, its callback will be called from the
* observer thread once the process is terminated.
*/
bool
sc_process_observer_init(struct sc_process_observer *observer, sc_pid pid,
const struct sc_process_listener *listener,
void *listener_userdata);
/**
* Wait for process termination until a deadline
*
* Return true if the process is already terminated. Return false if the
* process terminatation has not been detected yet (however, it may have
* terminated in the meantime).
*
* To wait without timeout/deadline, just use sc_process_wait() instead.
*/
bool
sc_process_observer_timedwait(struct sc_process_observer *observer,
sc_tick deadline);
/**
* Join the observer thread
*/
void
sc_process_observer_join(struct sc_process_observer *observer);
/**
* Destroy the observer
*
* This does not close the associated process.
*/
void
sc_process_observer_destroy(struct sc_process_observer *observer);
#endif #endif

View File

@ -1,50 +0,0 @@
#include "process_intr.h"
bool
sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid,
const char *name, bool close) {
if (!sc_intr_set_process(intr, pid)) {
// Already interrupted
return false;
}
// Always pass close=false, interrupting would be racy otherwise
bool ret = sc_process_check_success(pid, name, false);
sc_intr_set_process(intr, SC_PROCESS_NONE);
if (close) {
// Close separately
sc_process_close(pid);
}
return ret;
}
ssize_t
sc_pipe_read_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data,
size_t len) {
if (!sc_intr_set_process(intr, pid)) {
// Already interrupted
return false;
}
ssize_t ret = sc_pipe_read(pipe, data, len);
sc_intr_set_process(intr, SC_PROCESS_NONE);
return ret;
}
ssize_t
sc_pipe_read_all_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe,
char *data, size_t len) {
if (!sc_intr_set_process(intr, pid)) {
// Already interrupted
return false;
}
ssize_t ret = sc_pipe_read_all(pipe, data, len);
sc_intr_set_process(intr, SC_PROCESS_NONE);
return ret;
}

View File

@ -1,21 +0,0 @@
#ifndef SC_PROCESS_INTR_H
#define SC_PROCESS_INTR_H
#include "common.h"
#include "intr.h"
#include "process.h"
bool
sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid,
const char *name, bool close);
ssize_t
sc_pipe_read_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data,
size_t len);
ssize_t
sc_pipe_read_all_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe,
char *data, size_t len);
#endif

View File

@ -1,117 +0,0 @@
#ifndef SC_STR_H
#define SC_STR_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
/**
* Like strncpy(), except:
* - it copies at most n-1 chars
* - the dest string is nul-terminated
* - it does not write useless bytes if strlen(src) < n
* - it returns the number of chars actually written (max n-1) if src has
* been copied completely, or n if src has been truncated
*/
size_t
sc_strncpy(char *dest, const char *src, size_t n);
/**
* Join tokens by separator `sep` into `dst`
*
* Return the number of chars actually written (max n-1) if no truncation
* occurred, or n if truncated.
*/
size_t
sc_str_join(char *dst, const char *const tokens[], char sep, size_t n);
/**
* Quote a string
*
* Return a new allocated string, surrounded with quotes (`"`).
*/
char *
sc_str_quote(const char *src);
/**
* Parse `s` as an integer into `out`
*
* Return true if the conversion succeeded, false otherwise.
*/
bool
sc_str_parse_integer(const char *s, long *out);
/**
* Parse `s` as integers separated by `sep` (for example `1234:2000`) into `out`
*
* Returns the number of integers on success, 0 on failure.
*/
size_t
sc_str_parse_integers(const char *s, const char sep, size_t max_items,
long *out);
/**
* Parse `s` as an integer into `out`
*
* Like `sc_str_parse_integer()`, but accept 'k'/'K' (x1000) and 'm'/'M'
* (x1000000) as suffixes.
*
* Return true if the conversion succeeded, false otherwise.
*/
bool
sc_str_parse_integer_with_suffix(const char *s, long *out);
/**
* Search `s` in the list separated by `sep`
*
* For example, sc_str_list_contains("a,bc,def", ',', "bc") returns true.
*/
bool
sc_str_list_contains(const char *list, char sep, const char *s);
/**
* Return the index to truncate a UTF-8 string at a valid position
*/
size_t
sc_str_utf8_truncation_index(const char *utf8, size_t max_len);
#ifdef _WIN32
/**
* Convert a UTF-8 string to a wchar_t string
*
* Return the new allocated string, to be freed by the caller.
*/
wchar_t *
sc_str_to_wchars(const char *utf8);
/**
* Convert a wchar_t string to a UTF-8 string
*
* Return the new allocated string, to be freed by the caller.
*/
char *
sc_str_from_wchars(const wchar_t *s);
#endif
/**
* Wrap input lines to fit in `columns` columns
*
* Break input lines at word boundaries (spaces) so that they fit in `columns`
* columns, left-indented by `indent` spaces.
*/
char *
sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent);
/**
* Truncate the data after any of the characters from `endchars`
*
* An '\0' is always written at the end of the data, even if no newline
* character is encountered.
*
* Return the size of the resulting line.
*/
size_t
sc_str_truncate(char *data, size_t len, const char *endchars);
#endif

View File

@ -1,11 +1,9 @@
#include "str.h" #include "str_util.h"
#include <assert.h>
#include <errno.h> #include <errno.h>
#include <limits.h> #include <limits.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "util/strbuf.h"
#ifdef _WIN32 #ifdef _WIN32
# include <windows.h> # include <windows.h>
@ -13,7 +11,7 @@
#endif #endif
size_t size_t
sc_strncpy(char *dest, const char *src, size_t n) { xstrncpy(char *dest, const char *src, size_t n) {
size_t i; size_t i;
for (i = 0; i < n - 1 && src[i] != '\0'; ++i) for (i = 0; i < n - 1 && src[i] != '\0'; ++i)
dest[i] = src[i]; dest[i] = src[i];
@ -23,7 +21,7 @@ sc_strncpy(char *dest, const char *src, size_t n) {
} }
size_t size_t
sc_str_join(char *dst, const char *const tokens[], char sep, size_t n) { xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) {
const char *const *remaining = tokens; const char *const *remaining = tokens;
const char *token = *remaining++; const char *token = *remaining++;
size_t i = 0; size_t i = 0;
@ -33,7 +31,7 @@ sc_str_join(char *dst, const char *const tokens[], char sep, size_t n) {
if (i == n) if (i == n)
goto truncated; goto truncated;
} }
size_t w = sc_strncpy(dst + i, token, n - i); size_t w = xstrncpy(dst + i, token, n - i);
if (w >= n - i) if (w >= n - i)
goto truncated; goto truncated;
i += w; i += w;
@ -47,7 +45,7 @@ truncated:
} }
char * char *
sc_str_quote(const char *src) { strquote(const char *src) {
size_t len = strlen(src); size_t len = strlen(src);
char *quoted = malloc(len + 3); char *quoted = malloc(len + 3);
if (!quoted) { if (!quoted) {
@ -61,7 +59,7 @@ sc_str_quote(const char *src) {
} }
bool bool
sc_str_parse_integer(const char *s, long *out) { parse_integer(const char *s, long *out) {
char *endptr; char *endptr;
if (*s == '\0') { if (*s == '\0') {
return false; return false;
@ -80,8 +78,7 @@ sc_str_parse_integer(const char *s, long *out) {
} }
size_t size_t
sc_str_parse_integers(const char *s, const char sep, size_t max_items, parse_integers(const char *s, const char sep, size_t max_items, long *out) {
long *out) {
size_t count = 0; size_t count = 0;
char *endptr; char *endptr;
do { do {
@ -110,7 +107,7 @@ sc_str_parse_integers(const char *s, const char sep, size_t max_items,
} }
bool bool
sc_str_parse_integer_with_suffix(const char *s, long *out) { parse_integer_with_suffix(const char *s, long *out) {
char *endptr; char *endptr;
if (*s == '\0') { if (*s == '\0') {
return false; return false;
@ -144,7 +141,7 @@ sc_str_parse_integer_with_suffix(const char *s, long *out) {
} }
bool bool
sc_str_list_contains(const char *list, char sep, const char *s) { strlist_contains(const char *list, char sep, const char *s) {
char *p; char *p;
do { do {
p = strchr(list, sep); p = strchr(list, sep);
@ -162,7 +159,7 @@ sc_str_list_contains(const char *list, char sep, const char *s) {
} }
size_t size_t
sc_str_utf8_truncation_index(const char *utf8, size_t max_len) { utf8_truncation_index(const char *utf8, size_t max_len) {
size_t len = strlen(utf8); size_t len = strlen(utf8);
if (len <= max_len) { if (len <= max_len) {
return len; return len;
@ -180,7 +177,7 @@ sc_str_utf8_truncation_index(const char *utf8, size_t max_len) {
#ifdef _WIN32 #ifdef _WIN32
wchar_t * wchar_t *
sc_str_to_wchars(const char *utf8) { utf8_to_wide_char(const char *utf8) {
int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
if (!len) { if (!len) {
return NULL; return NULL;
@ -196,7 +193,7 @@ sc_str_to_wchars(const char *utf8) {
} }
char * char *
sc_str_from_wchars(const wchar_t *ws) { utf8_from_wide_char(const wchar_t *ws) {
int len = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL); int len = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL);
if (!len) { if (!len) {
return NULL; return NULL;
@ -212,90 +209,3 @@ sc_str_from_wchars(const wchar_t *ws) {
} }
#endif #endif
char *
sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent) {
assert(indent < columns);
struct sc_strbuf buf;
// The output string should not be much longer than the input string (just
// a few '\n' added), so this initial capacity should hopefully almost
// always avoid internal realloc() in string buffer
size_t cap = strlen(input) * 3 / 2;
if (!sc_strbuf_init(&buf, cap)) {
return false;
}
#define APPEND(S,N) if (!sc_strbuf_append(&buf, S, N)) goto error
#define APPEND_CHAR(C) if (!sc_strbuf_append_char(&buf, C)) goto error
#define APPEND_N(C,N) if (!sc_strbuf_append_n(&buf, C, N)) goto error
#define APPEND_INDENT() if (indent) APPEND_N(' ', indent)
APPEND_INDENT();
// The last separator encountered, it must be inserted only conditionnaly,
// depending on the next token
char pending = 0;
// col tracks the current column in the current line
size_t col = indent;
while (*input) {
size_t sep_idx = strcspn(input, "\n ");
size_t new_col = col + sep_idx;
if (pending == ' ') {
// The pending space counts
++new_col;
}
bool wrap = new_col > columns;
char sep = input[sep_idx];
if (sep == ' ')
sep = ' ';
if (wrap) {
APPEND_CHAR('\n');
APPEND_INDENT();
col = indent;
} else if (pending) {
APPEND_CHAR(pending);
++col;
if (pending == '\n')
{
APPEND_INDENT();
col = indent;
}
}
if (sep_idx) {
APPEND(input, sep_idx);
col += sep_idx;
}
pending = sep;
input += sep_idx;
if (*input != '\0') {
// Skip the separator
++input;
}
}
if (pending)
APPEND_CHAR(pending);
return buf.s;
error:
free(buf.s);
return NULL;
}
size_t
sc_str_truncate(char *data, size_t len, const char *endchars) {
data[len - 1] = '\0';
size_t idx = strcspn(data, endchars);
data[idx] = '\0';
return idx;
}

65
app/src/util/str_util.h Normal file
View File

@ -0,0 +1,65 @@
#ifndef STRUTIL_H
#define STRUTIL_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
// like strncpy, except:
// - it copies at most n-1 chars
// - the dest string is nul-terminated
// - it does not write useless bytes if strlen(src) < n
// - it returns the number of chars actually written (max n-1) if src has
// been copied completely, or n if src has been truncated
size_t
xstrncpy(char *dest, const char *src, size_t n);
// join tokens by sep into dst
// returns the number of chars actually written (max n-1) if no truncation
// occurred, or n if truncated
size_t
xstrjoin(char *dst, const char *const tokens[], char sep, size_t n);
// quote a string
// returns the new allocated string, to be freed by the caller
char *
strquote(const char *src);
// parse s as an integer into value
// returns true if the conversion succeeded, false otherwise
bool
parse_integer(const char *s, long *out);
// parse s as integers separated by sep (for example '1234:2000')
// returns the number of integers on success, 0 on failure
size_t
parse_integers(const char *s, const char sep, size_t max_items, long *out);
// parse s as an integer into value
// like parse_integer(), but accept 'k'/'K' (x1000) and 'm'/'M' (x1000000) as
// suffix
// returns true if the conversion succeeded, false otherwise
bool
parse_integer_with_suffix(const char *s, long *out);
// search s in the list separated by sep
// for example, strlist_contains("a,bc,def", ',', "bc") returns true
bool
strlist_contains(const char *list, char sep, const char *s);
// return the index to truncate a UTF-8 string at a valid position
size_t
utf8_truncation_index(const char *utf8, size_t max_len);
#ifdef _WIN32
// convert a UTF-8 string to a wchar_t string
// returns the new allocated string, to be freed by the caller
wchar_t *
utf8_to_wide_char(const char *utf8);
char *
utf8_from_wide_char(const wchar_t *s);
#endif
#endif

View File

@ -1,87 +0,0 @@
#include "strbuf.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
bool
sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap) {
buf->s = malloc(init_cap + 1); // +1 for '\0'
if (!buf->s) {
return false;
}
buf->len = 0;
buf->cap = init_cap;
return true;
}
static bool
sc_strbuf_reserve(struct sc_strbuf *buf, size_t len) {
if (buf->len + len > buf->cap) {
size_t new_cap = buf->cap * 3 / 2 + len;
char *s = realloc(buf->s, new_cap + 1); // +1 for '\0'
if (!s) {
// Leave the old buf->s
return false;
}
buf->s = s;
buf->cap = new_cap;
}
return true;
}
bool
sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len) {
assert(s);
assert(*s);
assert(strlen(s) >= len);
if (!sc_strbuf_reserve(buf, len)) {
return false;
}
memcpy(&buf->s[buf->len], s, len);
buf->len += len;
buf->s[buf->len] = '\0';
return true;
}
bool
sc_strbuf_append_char(struct sc_strbuf *buf, const char c) {
if (!sc_strbuf_reserve(buf, 1)) {
return false;
}
buf->s[buf->len] = c;
buf->len ++;
buf->s[buf->len] = '\0';
return true;
}
bool
sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n) {
if (!sc_strbuf_reserve(buf, n)) {
return false;
}
memset(&buf->s[buf->len], c, n);
buf->len += n;
buf->s[buf->len] = '\0';
return true;
}
void
sc_strbuf_shrink(struct sc_strbuf *buf) {
assert(buf->len <= buf->cap);
if (buf->len != buf->cap) {
char *s = realloc(buf->s, buf->len + 1); // +1 for '\0'
assert(s); // decreasing the size may not fail
buf->s = s;
buf->cap = buf->len;
}
}

View File

@ -1,73 +0,0 @@
#ifndef SC_STRBUF_H
#define SC_STRBUF_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
struct sc_strbuf {
char *s;
size_t len;
size_t cap;
};
/**
* Initialize the string buffer
*
* `buf->s` must be manually freed by the caller.
*/
bool
sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap);
/**
* Append a string
*
* Append `len` characters from `s` to the buffer.
*/
bool
sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len);
/**
* Append a char
*
* Append a single character to the buffer.
*/
bool
sc_strbuf_append_char(struct sc_strbuf *buf, const char c);
/**
* Append a char `n` times
*
* Append the same characters `n` times to the buffer.
*/
bool
sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n);
/**
* Append a NUL-terminated string
*/
static inline bool
sc_strbuf_append_str(struct sc_strbuf *buf, const char *s) {
return sc_strbuf_append(buf, s, strlen(s));
}
/**
* Append a static string
*
* Append a string whose size is known at compile time (for
* example a string literal).
*/
#define sc_strbuf_append_staticstr(BUF, S) \
sc_strbuf_append(BUF, S, sizeof(S) - 1)
/**
* Shrink the buffer capacity to its current length
*
* This resizes `buf->s` to fit the content.
*/
void
sc_strbuf_shrink(struct sc_strbuf *buf);
#endif

View File

@ -1,51 +0,0 @@
#include "term.h"
#include <assert.h>
#ifdef _WIN32
# include <windows.h>
#else
# include <unistd.h>
# include <sys/ioctl.h>
#endif
bool
sc_term_get_size(unsigned *rows, unsigned *cols) {
#ifdef _WIN32
CONSOLE_SCREEN_BUFFER_INFO csbi;
bool ok =
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
if (!ok) {
return false;
}
if (rows) {
assert(csbi.srWindow.Bottom >= csbi.srWindow.Top);
*rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
}
if (cols) {
assert(csbi.srWindow.Right >= csbi.srWindow.Left);
*cols = csbi.srWindow.Right - csbi.srWindow.Left + 1;
}
return true;
#else
struct winsize ws;
int r = ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
if (r == -1) {
return false;
}
if (rows) {
*rows = ws.ws_row;
}
if (cols) {
*cols = ws.ws_col;
}
return true;
#endif
}

View File

@ -1,21 +0,0 @@
#ifndef SC_TERM_H
#define SC_TERM_H
#include "common.h"
#include <stdbool.h>
/**
* Return the terminal dimensions
*
* Return false if the dimensions could not be retrieved.
*
* Otherwise, return true, and:
* - if `rows` is not NULL, then the number of rows is written to `*rows`.
* - if `columns` is not NULL, then the number of columns is written to
* `*columns`.
*/
bool
sc_term_get_size(unsigned *rows, unsigned *cols);
#endif

View File

@ -1,8 +1,6 @@
#ifndef SC_TICK_H #ifndef SC_TICK_H
#define SC_TICK_H #define SC_TICK_H
#include "common.h"
#include <stdint.h> #include <stdint.h>
typedef int64_t sc_tick; typedef int64_t sc_tick;

View File

@ -1,7 +1,7 @@
#include "v4l2_sink.h" #include "v4l2_sink.h"
#include "util/log.h" #include "util/log.h"
#include "util/str.h" #include "util/str_util.h"
/** Downcast frame_sink to sc_v4l2_sink */ /** Downcast frame_sink to sc_v4l2_sink */
#define DOWNCAST(SINK) container_of(SINK, struct sc_v4l2_sink, frame_sink) #define DOWNCAST(SINK) container_of(SINK, struct sc_v4l2_sink, frame_sink)
@ -21,7 +21,7 @@ find_muxer(const char *name) {
oformat = av_oformat_next(oformat); oformat = av_oformat_next(oformat);
#endif #endif
// until null or containing the requested name // until null or containing the requested name
} while (oformat && !sc_str_list_contains(oformat->name, ',', name)); } while (oformat && !strlist_contains(oformat->name, ',', name));
return oformat; return oformat;
} }
@ -359,7 +359,7 @@ sc_v4l2_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
bool bool
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name, sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
struct sc_size frame_size, sc_tick buffering_time) { struct size frame_size, sc_tick buffering_time) {
vs->device_name = strdup(device_name); vs->device_name = strdup(device_name);
if (!vs->device_name) { if (!vs->device_name) {
LOGE("Could not strdup v4l2 device name"); LOGE("Could not strdup v4l2 device name");

View File

@ -18,7 +18,7 @@ struct sc_v4l2_sink {
AVCodecContext *encoder_ctx; AVCodecContext *encoder_ctx;
char *device_name; char *device_name;
struct sc_size frame_size; struct size frame_size;
sc_tick buffering_time; sc_tick buffering_time;
sc_thread thread; sc_thread thread;
@ -34,7 +34,7 @@ struct sc_v4l2_sink {
bool bool
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name, sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
struct sc_size frame_size, sc_tick buffering_time); struct size frame_size, sc_tick buffering_time);
void void
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs); sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);

View File

@ -4,11 +4,11 @@
#include <string.h> #include <string.h>
#include "cli.h" #include "cli.h"
#include "options.h" #include "scrcpy.h"
static void test_flag_version(void) { static void test_flag_version(void) {
struct scrcpy_cli_args args = { struct scrcpy_cli_args args = {
.opts = scrcpy_options_default, .opts = SCRCPY_OPTIONS_DEFAULT,
.help = false, .help = false,
.version = false, .version = false,
}; };
@ -23,7 +23,7 @@ static void test_flag_version(void) {
static void test_flag_help(void) { static void test_flag_help(void) {
struct scrcpy_cli_args args = { struct scrcpy_cli_args args = {
.opts = scrcpy_options_default, .opts = SCRCPY_OPTIONS_DEFAULT,
.help = false, .help = false,
.version = false, .version = false,
}; };
@ -38,7 +38,7 @@ static void test_flag_help(void) {
static void test_options(void) { static void test_options(void) {
struct scrcpy_cli_args args = { struct scrcpy_cli_args args = {
.opts = scrcpy_options_default, .opts = SCRCPY_OPTIONS_DEFAULT,
.help = false, .help = false,
.version = false, .version = false,
}; };
@ -100,7 +100,7 @@ static void test_options(void) {
static void test_options2(void) { static void test_options2(void) {
struct scrcpy_cli_args args = { struct scrcpy_cli_args args = {
.opts = scrcpy_options_default, .opts = SCRCPY_OPTIONS_DEFAULT,
.help = false, .help = false,
.version = false, .version = false,
}; };

View File

@ -1,387 +0,0 @@
#include "common.h"
#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include "util/str.h"
static void test_strncpy_simple(void) {
char s[] = "xxxxxxxxxx";
size_t w = sc_strncpy(s, "abcdef", sizeof(s));
// returns strlen of copied string
assert(w == 6);
// is nul-terminated
assert(s[6] == '\0');
// does not write useless bytes
assert(s[7] == 'x');
// copies the content as expected
assert(!strcmp("abcdef", s));
}
static void test_strncpy_just_fit(void) {
char s[] = "xxxxxx";
size_t w = sc_strncpy(s, "abcdef", sizeof(s));
// returns strlen of copied string
assert(w == 6);
// is nul-terminated
assert(s[6] == '\0');
// copies the content as expected
assert(!strcmp("abcdef", s));
}
static void test_strncpy_truncated(void) {
char s[] = "xxx";
size_t w = sc_strncpy(s, "abcdef", sizeof(s));
// returns 'n' (sizeof(s))
assert(w == 4);
// is nul-terminated
assert(s[3] == '\0');
// copies the content as expected
assert(!strncmp("abcdef", s, 3));
}
static void test_join_simple(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxxxxxxxxx";
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
// returns strlen of concatenation
assert(w == 11);
// is nul-terminated
assert(s[11] == '\0');
// does not write useless bytes
assert(s[12] == 'x');
// copies the content as expected
assert(!strcmp("abc de fghi", s));
}
static void test_join_just_fit(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxxxxxx";
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
// returns strlen of concatenation
assert(w == 11);
// is nul-terminated
assert(s[11] == '\0');
// copies the content as expected
assert(!strcmp("abc de fghi", s));
}
static void test_join_truncated_in_token(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxx";
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
// returns 'n' (sizeof(s))
assert(w == 6);
// is nul-terminated
assert(s[5] == '\0');
// copies the content as expected
assert(!strcmp("abc d", s));
}
static void test_join_truncated_before_sep(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxx";
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
// returns 'n' (sizeof(s))
assert(w == 7);
// is nul-terminated
assert(s[6] == '\0');
// copies the content as expected
assert(!strcmp("abc de", s));
}
static void test_join_truncated_after_sep(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxx";
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
// returns 'n' (sizeof(s))
assert(w == 8);
// is nul-terminated
assert(s[7] == '\0');
// copies the content as expected
assert(!strcmp("abc de ", s));
}
static void test_quote(void) {
const char *s = "abcde";
char *out = sc_str_quote(s);
// add '"' at the beginning and the end
assert(!strcmp("\"abcde\"", out));
free(out);
}
static void test_utf8_truncate(void) {
const char *s = "aÉbÔc";
assert(strlen(s) == 7); // É and Ô are 2 bytes-wide
size_t count;
count = sc_str_utf8_truncation_index(s, 1);
assert(count == 1);
count = sc_str_utf8_truncation_index(s, 2);
assert(count == 1); // É is 2 bytes-wide
count = sc_str_utf8_truncation_index(s, 3);
assert(count == 3);
count = sc_str_utf8_truncation_index(s, 4);
assert(count == 4);
count = sc_str_utf8_truncation_index(s, 5);
assert(count == 4); // Ô is 2 bytes-wide
count = sc_str_utf8_truncation_index(s, 6);
assert(count == 6);
count = sc_str_utf8_truncation_index(s, 7);
assert(count == 7);
count = sc_str_utf8_truncation_index(s, 8);
assert(count == 7); // no more chars
}
static void test_parse_integer(void) {
long value;
bool ok = sc_str_parse_integer("1234", &value);
assert(ok);
assert(value == 1234);
ok = sc_str_parse_integer("-1234", &value);
assert(ok);
assert(value == -1234);
ok = sc_str_parse_integer("1234k", &value);
assert(!ok);
ok = sc_str_parse_integer("123456789876543212345678987654321", &value);
assert(!ok); // out-of-range
}
static void test_parse_integers(void) {
long values[5];
size_t count = sc_str_parse_integers("1234", ':', 5, values);
assert(count == 1);
assert(values[0] == 1234);
count = sc_str_parse_integers("1234:5678", ':', 5, values);
assert(count == 2);
assert(values[0] == 1234);
assert(values[1] == 5678);
count = sc_str_parse_integers("1234:5678", ':', 2, values);
assert(count == 2);
assert(values[0] == 1234);
assert(values[1] == 5678);
count = sc_str_parse_integers("1234:-5678", ':', 2, values);
assert(count == 2);
assert(values[0] == 1234);
assert(values[1] == -5678);
count = sc_str_parse_integers("1:2:3:4:5", ':', 5, values);
assert(count == 5);
assert(values[0] == 1);
assert(values[1] == 2);
assert(values[2] == 3);
assert(values[3] == 4);
assert(values[4] == 5);
count = sc_str_parse_integers("1234:5678", ':', 1, values);
assert(count == 0); // max_items == 1
count = sc_str_parse_integers("1:2:3:4:5", ':', 3, values);
assert(count == 0); // max_items == 3
count = sc_str_parse_integers(":1234", ':', 5, values);
assert(count == 0); // invalid
count = sc_str_parse_integers("1234:", ':', 5, values);
assert(count == 0); // invalid
count = sc_str_parse_integers("1234:", ':', 1, values);
assert(count == 0); // invalid, even when max_items == 1
count = sc_str_parse_integers("1234::5678", ':', 5, values);
assert(count == 0); // invalid
}
static void test_parse_integer_with_suffix(void) {
long value;
bool ok = sc_str_parse_integer_with_suffix("1234", &value);
assert(ok);
assert(value == 1234);
ok = sc_str_parse_integer_with_suffix("-1234", &value);
assert(ok);
assert(value == -1234);
ok = sc_str_parse_integer_with_suffix("1234k", &value);
assert(ok);
assert(value == 1234000);
ok = sc_str_parse_integer_with_suffix("1234m", &value);
assert(ok);
assert(value == 1234000000);
ok = sc_str_parse_integer_with_suffix("-1234k", &value);
assert(ok);
assert(value == -1234000);
ok = sc_str_parse_integer_with_suffix("-1234m", &value);
assert(ok);
assert(value == -1234000000);
ok = sc_str_parse_integer_with_suffix("123456789876543212345678987654321", &value);
assert(!ok); // out-of-range
char buf[32];
sprintf(buf, "%ldk", LONG_MAX / 2000);
ok = sc_str_parse_integer_with_suffix(buf, &value);
assert(ok);
assert(value == LONG_MAX / 2000 * 1000);
sprintf(buf, "%ldm", LONG_MAX / 2000);
ok = sc_str_parse_integer_with_suffix(buf, &value);
assert(!ok);
sprintf(buf, "%ldk", LONG_MIN / 2000);
ok = sc_str_parse_integer_with_suffix(buf, &value);
assert(ok);
assert(value == LONG_MIN / 2000 * 1000);
sprintf(buf, "%ldm", LONG_MIN / 2000);
ok = sc_str_parse_integer_with_suffix(buf, &value);
assert(!ok);
}
static void test_strlist_contains(void) {
assert(sc_str_list_contains("a,bc,def", ',', "bc"));
assert(!sc_str_list_contains("a,bc,def", ',', "b"));
assert(sc_str_list_contains("", ',', ""));
assert(sc_str_list_contains("abc,", ',', ""));
assert(sc_str_list_contains(",abc", ',', ""));
assert(sc_str_list_contains("abc,,def", ',', ""));
assert(!sc_str_list_contains("abc", ',', ""));
assert(sc_str_list_contains(",,|x", '|', ",,"));
assert(sc_str_list_contains("xyz", '\0', "xyz"));
}
static void test_wrap_lines(void) {
const char *s = "This is a text to test line wrapping. The lines must be "
"wrapped at a space or a line break.\n"
"\n"
"This rectangle must remains a rectangle because it is "
"drawn in lines having lengths lower than the specified "
"number of columns:\n"
" +----+\n"
" | |\n"
" +----+\n";
// |---- 1 1 2 2|
// |0 5 0 5 0 3| <-- 24 columns
const char *expected = " This is a text to\n"
" test line wrapping.\n"
" The lines must be\n"
" wrapped at a space\n"
" or a line break.\n"
" \n"
" This rectangle must\n"
" remains a rectangle\n"
" because it is drawn\n"
" in lines having\n"
" lengths lower than\n"
" the specified number\n"
" of columns:\n"
" +----+\n"
" | |\n"
" +----+\n";
char *formatted = sc_str_wrap_lines(s, 24, 4);
assert(formatted);
assert(!strcmp(formatted, expected));
free(formatted);
}
static void test_truncate(void) {
char s[] = "hello\nworld\n!";
size_t len = sc_str_truncate(s, sizeof(s), "\n");
assert(len == 5);
assert(!strcmp("hello", s));
char s2[] = "hello\r\nworkd\r\n!";
len = sc_str_truncate(s2, sizeof(s2), "\n\r");
assert(len == 5);
assert(!strcmp("hello", s));
char s3[] = "hello world\n!";
len = sc_str_truncate(s3, sizeof(s3), " \n\r");
assert(len == 5);
assert(!strcmp("hello", s3));
char s4[] = "hello ";
len = sc_str_truncate(s4, sizeof(s4), " \n\r");
assert(len == 5);
assert(!strcmp("hello", s4));
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_strncpy_simple();
test_strncpy_just_fit();
test_strncpy_truncated();
test_join_simple();
test_join_just_fit();
test_join_truncated_in_token();
test_join_truncated_before_sep();
test_join_truncated_after_sep();
test_quote();
test_utf8_truncate();
test_parse_integer();
test_parse_integers();
test_parse_integer_with_suffix();
test_strlist_contains();
test_wrap_lines();
test_truncate();
return 0;
}

View File

@ -1,47 +0,0 @@
#include "common.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "util/strbuf.h"
static void test_strbuf_simple(void) {
struct sc_strbuf buf;
bool ok = sc_strbuf_init(&buf, 10);
assert(ok);
ok = sc_strbuf_append_staticstr(&buf, "Hello");
assert(ok);
ok = sc_strbuf_append_char(&buf, ' ');
assert(ok);
ok = sc_strbuf_append_staticstr(&buf, "world");
assert(ok);
ok = sc_strbuf_append_staticstr(&buf, "!\n");
assert(ok);
ok = sc_strbuf_append_staticstr(&buf, "This is a test");
assert(ok);
ok = sc_strbuf_append_n(&buf, '.', 3);
assert(ok);
assert(!strcmp(buf.s, "Hello world!\nThis is a test..."));
sc_strbuf_shrink(&buf);
assert(buf.len == buf.cap);
assert(!strcmp(buf.s, "Hello world!\nThis is a test..."));
free(buf.s);
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_strbuf_simple();
return 0;
}

321
app/tests/test_strutil.c Normal file
View File

@ -0,0 +1,321 @@
#include "common.h"
#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include "util/str_util.h"
static void test_xstrncpy_simple(void) {
char s[] = "xxxxxxxxxx";
size_t w = xstrncpy(s, "abcdef", sizeof(s));
// returns strlen of copied string
assert(w == 6);
// is nul-terminated
assert(s[6] == '\0');
// does not write useless bytes
assert(s[7] == 'x');
// copies the content as expected
assert(!strcmp("abcdef", s));
}
static void test_xstrncpy_just_fit(void) {
char s[] = "xxxxxx";
size_t w = xstrncpy(s, "abcdef", sizeof(s));
// returns strlen of copied string
assert(w == 6);
// is nul-terminated
assert(s[6] == '\0');
// copies the content as expected
assert(!strcmp("abcdef", s));
}
static void test_xstrncpy_truncated(void) {
char s[] = "xxx";
size_t w = xstrncpy(s, "abcdef", sizeof(s));
// returns 'n' (sizeof(s))
assert(w == 4);
// is nul-terminated
assert(s[3] == '\0');
// copies the content as expected
assert(!strncmp("abcdef", s, 3));
}
static void test_xstrjoin_simple(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxxxxxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
// returns strlen of concatenation
assert(w == 11);
// is nul-terminated
assert(s[11] == '\0');
// does not write useless bytes
assert(s[12] == 'x');
// copies the content as expected
assert(!strcmp("abc de fghi", s));
}
static void test_xstrjoin_just_fit(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
// returns strlen of concatenation
assert(w == 11);
// is nul-terminated
assert(s[11] == '\0');
// copies the content as expected
assert(!strcmp("abc de fghi", s));
}
static void test_xstrjoin_truncated_in_token(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
// returns 'n' (sizeof(s))
assert(w == 6);
// is nul-terminated
assert(s[5] == '\0');
// copies the content as expected
assert(!strcmp("abc d", s));
}
static void test_xstrjoin_truncated_before_sep(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
// returns 'n' (sizeof(s))
assert(w == 7);
// is nul-terminated
assert(s[6] == '\0');
// copies the content as expected
assert(!strcmp("abc de", s));
}
static void test_xstrjoin_truncated_after_sep(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxx";
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
// returns 'n' (sizeof(s))
assert(w == 8);
// is nul-terminated
assert(s[7] == '\0');
// copies the content as expected
assert(!strcmp("abc de ", s));
}
static void test_strquote(void) {
const char *s = "abcde";
char *out = strquote(s);
// add '"' at the beginning and the end
assert(!strcmp("\"abcde\"", out));
free(out);
}
static void test_utf8_truncate(void) {
const char *s = "aÉbÔc";
assert(strlen(s) == 7); // É and Ô are 2 bytes-wide
size_t count;
count = utf8_truncation_index(s, 1);
assert(count == 1);
count = utf8_truncation_index(s, 2);
assert(count == 1); // É is 2 bytes-wide
count = utf8_truncation_index(s, 3);
assert(count == 3);
count = utf8_truncation_index(s, 4);
assert(count == 4);
count = utf8_truncation_index(s, 5);
assert(count == 4); // Ô is 2 bytes-wide
count = utf8_truncation_index(s, 6);
assert(count == 6);
count = utf8_truncation_index(s, 7);
assert(count == 7);
count = utf8_truncation_index(s, 8);
assert(count == 7); // no more chars
}
static void test_parse_integer(void) {
long value;
bool ok = parse_integer("1234", &value);
assert(ok);
assert(value == 1234);
ok = parse_integer("-1234", &value);
assert(ok);
assert(value == -1234);
ok = parse_integer("1234k", &value);
assert(!ok);
ok = parse_integer("123456789876543212345678987654321", &value);
assert(!ok); // out-of-range
}
static void test_parse_integers(void) {
long values[5];
size_t count = parse_integers("1234", ':', 5, values);
assert(count == 1);
assert(values[0] == 1234);
count = parse_integers("1234:5678", ':', 5, values);
assert(count == 2);
assert(values[0] == 1234);
assert(values[1] == 5678);
count = parse_integers("1234:5678", ':', 2, values);
assert(count == 2);
assert(values[0] == 1234);
assert(values[1] == 5678);
count = parse_integers("1234:-5678", ':', 2, values);
assert(count == 2);
assert(values[0] == 1234);
assert(values[1] == -5678);
count = parse_integers("1:2:3:4:5", ':', 5, values);
assert(count == 5);
assert(values[0] == 1);
assert(values[1] == 2);
assert(values[2] == 3);
assert(values[3] == 4);
assert(values[4] == 5);
count = parse_integers("1234:5678", ':', 1, values);
assert(count == 0); // max_items == 1
count = parse_integers("1:2:3:4:5", ':', 3, values);
assert(count == 0); // max_items == 3
count = parse_integers(":1234", ':', 5, values);
assert(count == 0); // invalid
count = parse_integers("1234:", ':', 5, values);
assert(count == 0); // invalid
count = parse_integers("1234:", ':', 1, values);
assert(count == 0); // invalid, even when max_items == 1
count = parse_integers("1234::5678", ':', 5, values);
assert(count == 0); // invalid
}
static void test_parse_integer_with_suffix(void) {
long value;
bool ok = parse_integer_with_suffix("1234", &value);
assert(ok);
assert(value == 1234);
ok = parse_integer_with_suffix("-1234", &value);
assert(ok);
assert(value == -1234);
ok = parse_integer_with_suffix("1234k", &value);
assert(ok);
assert(value == 1234000);
ok = parse_integer_with_suffix("1234m", &value);
assert(ok);
assert(value == 1234000000);
ok = parse_integer_with_suffix("-1234k", &value);
assert(ok);
assert(value == -1234000);
ok = parse_integer_with_suffix("-1234m", &value);
assert(ok);
assert(value == -1234000000);
ok = parse_integer_with_suffix("123456789876543212345678987654321", &value);
assert(!ok); // out-of-range
char buf[32];
sprintf(buf, "%ldk", LONG_MAX / 2000);
ok = parse_integer_with_suffix(buf, &value);
assert(ok);
assert(value == LONG_MAX / 2000 * 1000);
sprintf(buf, "%ldm", LONG_MAX / 2000);
ok = parse_integer_with_suffix(buf, &value);
assert(!ok);
sprintf(buf, "%ldk", LONG_MIN / 2000);
ok = parse_integer_with_suffix(buf, &value);
assert(ok);
assert(value == LONG_MIN / 2000 * 1000);
sprintf(buf, "%ldm", LONG_MIN / 2000);
ok = parse_integer_with_suffix(buf, &value);
assert(!ok);
}
static void test_strlist_contains(void) {
assert(strlist_contains("a,bc,def", ',', "bc"));
assert(!strlist_contains("a,bc,def", ',', "b"));
assert(strlist_contains("", ',', ""));
assert(strlist_contains("abc,", ',', ""));
assert(strlist_contains(",abc", ',', ""));
assert(strlist_contains("abc,,def", ',', ""));
assert(!strlist_contains("abc", ',', ""));
assert(strlist_contains(",,|x", '|', ",,"));
assert(strlist_contains("xyz", '\0', "xyz"));
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_xstrncpy_simple();
test_xstrncpy_just_fit();
test_xstrncpy_truncated();
test_xstrjoin_simple();
test_xstrjoin_just_fit();
test_xstrjoin_truncated_in_token();
test_xstrjoin_truncated_before_sep();
test_xstrjoin_truncated_after_sep();
test_strquote();
test_utf8_truncate();
test_parse_integer();
test_parse_integers();
test_parse_integer_with_suffix();
test_strlist_contains();
return 0;
}

View File

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

View File

@ -2,7 +2,7 @@ apply plugin: 'checkstyle'
check.dependsOn 'checkstyle' check.dependsOn 'checkstyle'
checkstyle { checkstyle {
toolVersion = '9.0.1' toolVersion = '6.19'
} }
task checkstyle(type: Checkstyle) { task checkstyle(type: Checkstyle) {

View File

@ -37,14 +37,6 @@ page at http://checkstyle.sourceforge.net/config.html -->
<module name="SuppressWarningsFilter"/> <module name="SuppressWarningsFilter"/>
<module name="LineLength">
<!-- what is a good max value? -->
<property name="max" value="150" />
<!-- ignore lines like "$File: //depot/... $" -->
<property name="ignorePattern" value="\$File.*\$" />
<property name="severity" value="info" />
</module>
<module name="TreeWalker"> <module name="TreeWalker">
<!-- Checks for Naming Conventions. --> <!-- Checks for Naming Conventions. -->
@ -80,6 +72,13 @@ page at http://checkstyle.sourceforge.net/config.html -->
<!-- Checks for Size Violations. --> <!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sf.net/config_sizes.html --> <!-- See http://checkstyle.sf.net/config_sizes.html -->
<module name="LineLength">
<!-- what is a good max value? -->
<property name="max" value="150" />
<!-- ignore lines like "$File: //depot/... $" -->
<property name="ignorePattern" value="\$File.*\$" />
<property name="severity" value="info" />
</module>
<module name="MethodLength" /> <module name="MethodLength" />
<module name="ParameterNumber"> <module name="ParameterNumber">
<property name="ignoreOverriddenMethods" value="true"/> <property name="ignoreOverriddenMethods" value="true"/>
@ -153,6 +152,26 @@ page at http://checkstyle.sourceforge.net/config.html -->
</module> </module>
<module name="UpperEll" /> <module name="UpperEll" />
<module name="FileContentsHolder" />
<!-- Required by comment suppression filters -->
</module>
<module name="SuppressionFilter">
<!--<property name="file" value="team-props/checkstyle/checkstyle-suppressions.xml" />-->
</module>
<!-- Enable suppression comments -->
<module name="SuppressionCommentFilter">
<property name="offCommentFormat" value="CHECKSTYLE IGNORE\s+(\S+)" />
<property name="onCommentFormat" value="CHECKSTYLE END IGNORE\s+(\S+)" />
<property name="checkFormat" value="$1" />
</module>
<module name="SuppressWithNearbyCommentFilter">
<!-- Syntax is "SUPPRESS CHECKSTYLE name" -->
<property name="commentFormat" value="SUPPRESS CHECKSTYLE (\w+)" />
<property name="checkFormat" value="$1" />
<property name="influenceFormat" value="1" />
</module> </module>
</module> </module>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -1,16 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" version="1.1">
<path style="opacity:0.2" d="m 16.846877,12 c -1.116351,0 -2.227419,0.912229 -2.015075,2 l 3.122973,16 -5.596557,11.109375 C 11.959876,41.871734 11.885244,42.336988 12.177176,43 c 0.278672,0.632897 0.998812,1 1.747448,1 H 24 34.075375 c 0.748637,0 1.468777,-0.367103 1.747448,-1 0.291932,-0.663012 0.217302,-1.128266 -0.181041,-1.890625 L 30.045225,30 33.168198,14 c 0.212344,-1.087771 -0.898724,-2 -2.015075,-2 H 24 Z"/>
<path style="fill:#cccccc" d="m 16.846877,11 c -1.116351,0 -2.227419,0.912229 -2.015075,2 l 3.122973,16 -5.596557,11.109375 C 11.959876,40.871734 11.885244,41.336988 12.177176,42 c 0.278672,0.632897 0.998812,1 1.747448,1 H 24 34.075375 c 0.748637,0 1.468777,-0.367103 1.747448,-1 0.291932,-0.663012 0.217302,-1.128266 -0.181041,-1.890625 L 30.045225,29 33.168198,13 c 0.212344,-1.087771 -0.898724,-2 -2.015075,-2 H 24 Z"/>
<rect style="opacity:0.2" width="40" height="32" x="4" y="6" rx="2" ry="2"/>
<path style="fill:#e4e4e4" d="m 4,33 v 2 c 0,1.108 0.892,2 2,2 h 36 c 1.108,0 2,-0.892 2,-2 v -2 z"/>
<path style="opacity:0.1" d="m 11.494141,15 a 1.5,1.5 0 0 0 -0.832032,0.255859 1.5,1.5 0 0 0 -0.40625,2.082032 l 3.13086,4.654297 C 10.404945,24.606192 8.4012371,28.299159 8.0019531,32.460938 7.9284599,34.000879 9.5546875,34 9.5546875,34 H 38.40625 c 0,0 1.672856,-3.38e-4 1.591797,-1.617188 -0.416529,-4.131451 -2.415618,-7.796833 -5.380859,-10.394531 l 3.126953,-4.65039 a 1.5,1.5 0 0 0 -0.40625,-2.082032 1.5,1.5 0 0 0 -1.125,-0.228515 1.5,1.5 0 0 0 -0.957032,0.634765 l -3.072265,4.566407 C 29.78649,18.814887 26.990024,18 24.001953,18 c -2.989385,0 -5.786177,0.815488 -8.183594,2.230469 l -3.074218,-4.56836 A 1.5,1.5 0 0 0 11.787109,15.027344 1.5,1.5 0 0 0 11.494141,15 Z"/>
<path style="fill:#077063" d="M 6,5 C 4.892,5 4,5.892 4,7 V 33 H 44 V 7 C 44,5.892 43.108,5 42,5 Z"/>
<path style="opacity:0.1;fill:#ffffff" d="M 6,5 C 4.892,5 4,5.892 4,7 V 8 C 4,6.892 4.892,6 6,6 h 36 c 1.108,0 2,0.892 2,2 V 7 C 44,5.892 43.108,5 42,5 Z"/>
<path style="fill:none;stroke:#30dd81;stroke-width:3;stroke-linecap:round" d="M 15.1998,21.000026 11.5,15.5"/>
<path style="fill:none;stroke:#30dd81;stroke-width:3;stroke-linecap:round" d="M 32.799764,21.000026 36.5,15.5"/>
<path style="fill:#30dd81" d="m 24.002386,17.000034 c -8.355868,0 -15.2214979,6.346843 -15.9999669,14.460906 C 7.9289259,33.000882 9.5544999,33 9.5544999,33 H 38.406003 c 0,0 1.672201,-3.35e-4 1.591142,-1.617185 C 39.182807,23.305596 32.331836,17.000034 24.002386,17.000034 Z"/>
<path style="opacity:0.2" d="m 16,25 a 1.9999959,1.9999959 0 0 0 -2,2 1.9999959,1.9999959 0 0 0 2,2 1.9999959,1.9999959 0 0 0 2,-2 1.9999959,1.9999959 0 0 0 -2,-2 z m 16,0 a 1.9999959,1.9999959 0 0 0 -2,2 1.9999959,1.9999959 0 0 0 2,2 1.9999959,1.9999959 0 0 0 2,-2 1.9999959,1.9999959 0 0 0 -2,-2 z"/>
<path style="fill:#ffffff" d="M 15.999996,24.000008 A 1.9999959,1.9999959 0 0 1 17.999992,26.000004 1.9999959,1.9999959 0 0 1 15.999996,28 1.9999959,1.9999959 0 0 1 14,26.000004 1.9999959,1.9999959 0 0 1 15.999996,24.000008 Z"/>
<path style="fill:#ffffff" d="M 31.999996,24.000008 A 1.9999959,1.9999959 0 0 1 33.999991,26.000004 1.9999959,1.9999959 0 0 1 31.999996,28 1.9999959,1.9999959 0 0 1 30,26.000004 1.9999959,1.9999959 0 0 1 31.999996,24.000008 Z"/>
<path style="fill:#ffffff;opacity:0.2" d="M 11.494141 14 A 1.5 1.5 0 0 0 10.662109 14.255859 A 1.5 1.5 0 0 0 10.115234 16.001953 A 1.5 1.5 0 0 1 10.662109 15.255859 A 1.5 1.5 0 0 1 11.494141 15 A 1.5 1.5 0 0 1 11.787109 15.027344 A 1.5 1.5 0 0 1 12.744141 15.662109 L 15.818359 20.230469 C 18.215776 18.815488 21.012568 18 24.001953 18 C 26.990024 18 29.78649 18.814887 32.183594 20.228516 L 35.255859 15.662109 A 1.5 1.5 0 0 1 36.212891 15.027344 A 1.5 1.5 0 0 1 37.337891 15.255859 A 1.5 1.5 0 0 1 37.910156 16.001953 A 1.5 1.5 0 0 0 37.337891 14.255859 A 1.5 1.5 0 0 0 36.212891 14.027344 A 1.5 1.5 0 0 0 35.255859 14.662109 L 32.183594 19.228516 C 29.78649 17.814887 26.990024 17 24.001953 17 C 21.012568 17 18.215776 17.815488 15.818359 19.230469 L 12.744141 14.662109 A 1.5 1.5 0 0 0 11.787109 14.027344 A 1.5 1.5 0 0 0 11.494141 14 z M 35.033203 21.369141 L 34.617188 21.988281 C 37.477056 24.493668 39.433905 27.993356 39.943359 31.945312 C 39.986866 31.783283 40.008864 31.598575 39.998047 31.382812 C 39.601372 27.448291 37.768055 23.938648 35.033203 21.369141 z M 12.970703 21.373047 C 10.220358 23.959215 8.3822757 27.496796 8.0019531 31.460938 C 7.9920657 31.668114 8.0150508 31.844846 8.0585938 32 C 8.5570234 28.027243 10.515755 24.509049 13.386719 21.992188 L 12.970703 21.373047 z"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -2,8 +2,8 @@
set -e set -e
BUILDDIR=build-auto BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.20/scrcpy-server-v1.20 PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.19/scrcpy-server-v1.19
PREBUILT_SERVER_SHA256=b20aee4951f99b060c4a44000ba94de973f9604758ef62beb253b371aad3df34 PREBUILT_SERVER_SHA256=876f9322182e6aac6a58db1334f4225855ef3a17eaebc80aab6601d9d1ecb867
echo "[scrcpy] Downloading prebuilt server..." echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

@ -1,5 +1,5 @@
project('scrcpy', 'c', project('scrcpy', 'c',
version: '1.20', version: '1.19',
meson_version: '>= 0.48', meson_version: '>= 0.48',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',

View File

@ -94,7 +94,6 @@ dist-win32: build-server build-win32
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)" cp data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)"
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)" cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)"
cp data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
@ -111,7 +110,6 @@ dist-win64: build-server build-win64
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)" cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)"
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)" cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
cp data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"

4
run
View File

@ -20,6 +20,4 @@ then
exit 1 exit 1
fi fi
SCRCPY_ICON_PATH="data/icon.png" \ SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" "$BUILDDIR/app/scrcpy" "$@"
SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" \
"$BUILDDIR/app/scrcpy" "$@"

View File

@ -1,13 +1,13 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 31 compileSdkVersion 30
defaultConfig { defaultConfig {
applicationId "com.genymobile.scrcpy" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 30
versionCode 12000 versionCode 11900
versionName "1.20" versionName "1.19"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
@ -20,7 +20,7 @@ android {
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.13.1' testImplementation 'junit:junit:4.13'
} }
apply from: "$project.rootDir/config/android-checkstyle.gradle" apply from: "$project.rootDir/config/android-checkstyle.gradle"

View File

@ -12,17 +12,15 @@
set -e set -e
SCRCPY_DEBUG=false SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=1.20 SCRCPY_VERSION_NAME=1.19
PLATFORM_VERSION=31 PLATFORM=${ANDROID_PLATFORM:-30}
PLATFORM=${ANDROID_PLATFORM:-$PLATFORM_VERSION} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-30.0.0}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0}
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})" BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
CLASSES_DIR="$BUILD_DIR/classes" CLASSES_DIR="$BUILD_DIR/classes"
SERVER_DIR=$(dirname "$0") SERVER_DIR=$(dirname "$0")
SERVER_BINARY=scrcpy-server SERVER_BINARY=scrcpy-server
ANDROID_JAR="$ANDROID_HOME/platforms/android-$PLATFORM/android.jar"
echo "Platform: android-$PLATFORM" echo "Platform: android-$PLATFORM"
echo "Build-tools: $BUILD_TOOLS" echo "Build-tools: $BUILD_TOOLS"
@ -49,17 +47,13 @@ cd "$SERVER_DIR/src/main/aidl"
echo "Compiling java sources..." echo "Compiling java sources..."
cd ../java cd ../java
javac -bootclasspath "$ANDROID_JAR" -cp "$CLASSES_DIR" -d "$CLASSES_DIR" \ javac -bootclasspath "$ANDROID_HOME/platforms/android-$PLATFORM/android.jar" \
-source 1.8 -target 1.8 \ -cp "$CLASSES_DIR" -d "$CLASSES_DIR" -source 1.8 -target 1.8 \
com/genymobile/scrcpy/*.java \ com/genymobile/scrcpy/*.java \
com/genymobile/scrcpy/wrappers/*.java com/genymobile/scrcpy/wrappers/*.java
echo "Dexing..." echo "Dexing..."
cd "$CLASSES_DIR" cd "$CLASSES_DIR"
if [[ $PLATFORM_VERSION -lt 31 ]]
then
# use dx
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \ "$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
--output "$BUILD_DIR/classes.dex" \ --output "$BUILD_DIR/classes.dex" \
android/view/*.class \ android/view/*.class \
@ -71,18 +65,5 @@ then
cd "$BUILD_DIR" cd "$BUILD_DIR"
jar cvf "$SERVER_BINARY" classes.dex jar cvf "$SERVER_BINARY" classes.dex
rm -rf classes.dex classes rm -rf classes.dex classes
else
# use d8
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/d8" --classpath "$ANDROID_JAR" \
--output "$BUILD_DIR/classes.zip" \
android/view/*.class \
android/content/*.class \
com/genymobile/scrcpy/*.class \
com/genymobile/scrcpy/wrappers/*.class
cd "$BUILD_DIR"
mv classes.zip "$SERVER_BINARY"
rm -rf classes
fi
echo "Server generated in $BUILD_DIR/$SERVER_BINARY" echo "Server generated in $BUILD_DIR/$SERVER_BINARY"

View File

@ -1,5 +1,6 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ContentProvider;
import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.os.Parcel; import android.os.Parcel;
@ -165,21 +166,14 @@ public final class CleanUp {
if (config.disableShowTouches || config.restoreStayOn != -1) { if (config.disableShowTouches || config.restoreStayOn != -1) {
ServiceManager serviceManager = new ServiceManager(); ServiceManager serviceManager = new ServiceManager();
Settings settings = new Settings(serviceManager); try (ContentProvider settings = serviceManager.getActivityManager().createSettingsProvider()) {
if (config.disableShowTouches) { if (config.disableShowTouches) {
Ln.i("Disabling \"show touches\""); Ln.i("Disabling \"show touches\"");
try { settings.putValue(ContentProvider.TABLE_SYSTEM, "show_touches", "0");
settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0");
} catch (SettingsException e) {
Ln.e("Could not restore \"show_touches\"", e);
}
} }
if (config.restoreStayOn != -1) { if (config.restoreStayOn != -1) {
Ln.i("Restoring \"stay awake\""); Ln.i("Restoring \"stay awake\"");
try { settings.putValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
} catch (SettingsException e) {
Ln.e("Could not restore \"stay_on_while_plugged_in\"", e);
} }
} }
} }

View File

@ -1,33 +0,0 @@
package com.genymobile.scrcpy;
import java.io.IOException;
import java.util.Arrays;
import java.util.Scanner;
public final class Command {
private Command() {
// not instantiable
}
public static void exec(String... cmd) throws IOException, InterruptedException {
Process process = Runtime.getRuntime().exec(cmd);
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode);
}
}
public static String execReadLine(String... cmd) throws IOException, InterruptedException {
String result = null;
Process process = Runtime.getRuntime().exec(cmd);
Scanner scanner = new Scanner(process.getInputStream());
if (scanner.hasNextLine()) {
result = scanner.nextLine();
}
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode);
}
return result;
}
}

View File

@ -1,6 +1,7 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ClipboardManager; import com.genymobile.scrcpy.wrappers.ClipboardManager;
import com.genymobile.scrcpy.wrappers.ContentProvider;
import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.ServiceManager;
import com.genymobile.scrcpy.wrappers.SurfaceControl; import com.genymobile.scrcpy.wrappers.SurfaceControl;
@ -28,7 +29,6 @@ public final class Device {
public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2; public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
private static final ServiceManager SERVICE_MANAGER = new ServiceManager(); private static final ServiceManager SERVICE_MANAGER = new ServiceManager();
private static final Settings SETTINGS = new Settings(SERVICE_MANAGER);
public interface RotationListener { public interface RotationListener {
void onRotationChanged(int rotation); void onRotationChanged(int rotation);
@ -296,7 +296,7 @@ public final class Device {
} }
} }
public static Settings getSettings() { public static ContentProvider createSettingsProvider() {
return SETTINGS; return SERVICE_MANAGER.getActivityManager().createSettingsProvider();
} }
} }

View File

@ -57,18 +57,11 @@ public final class Ln {
} }
} }
public static void w(String message, Throwable throwable) {
if (isEnabled(Level.WARN)) {
Log.w(TAG, message, throwable);
System.out.println(PREFIX + "WARN: " + message);
if (throwable != null) {
throwable.printStackTrace();
}
}
}
public static void w(String message) { public static void w(String message) {
w(message, null); if (isEnabled(Level.WARN)) {
Log.w(TAG, message);
System.out.println(PREFIX + "WARN: " + message);
}
} }
public static void e(String message, Throwable throwable) { public static void e(String message, Throwable throwable) {

View File

@ -1,5 +1,7 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ContentProvider;
import android.graphics.Rect; import android.graphics.Rect;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCodecInfo; import android.media.MediaCodecInfo;
@ -17,25 +19,24 @@ public final class Server {
// not instantiable // not instantiable
} }
private static void initAndCleanUp(Options options) { private static void scrcpy(Options options) throws IOException {
Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")");
final Device device = new Device(options);
List<CodecOption> codecOptions = CodecOption.parse(options.getCodecOptions());
boolean mustDisableShowTouchesOnCleanUp = false; boolean mustDisableShowTouchesOnCleanUp = false;
int restoreStayOn = -1; int restoreStayOn = -1;
if (options.getShowTouches() || options.getStayAwake()) { if (options.getShowTouches() || options.getStayAwake()) {
Settings settings = Device.getSettings(); try (ContentProvider settings = Device.createSettingsProvider()) {
if (options.getShowTouches()) { if (options.getShowTouches()) {
try { String oldValue = settings.getAndPutValue(ContentProvider.TABLE_SYSTEM, "show_touches", "1");
String oldValue = settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1");
// If "show touches" was disabled, it must be disabled back on clean up // If "show touches" was disabled, it must be disabled back on clean up
mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue); mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue);
} catch (SettingsException e) {
Ln.e("Could not change \"show_touches\"", e);
}
} }
if (options.getStayAwake()) { if (options.getStayAwake()) {
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS; int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
try { String oldValue = settings.getAndPutValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
String oldValue = settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
try { try {
restoreStayOn = Integer.parseInt(oldValue); restoreStayOn = Integer.parseInt(oldValue);
if (restoreStayOn == stayOn) { if (restoreStayOn == stayOn) {
@ -45,25 +46,11 @@ public final class Server {
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
restoreStayOn = 0; restoreStayOn = 0;
} }
} catch (SettingsException e) {
Ln.e("Could not change \"stay_on_while_plugged_in\"", e);
} }
} }
} }
try {
CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, true, options.getPowerOffScreenOnClose()); CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, true, options.getPowerOffScreenOnClose());
} catch (IOException e) {
Ln.e("Could not configure cleanup", e);
}
}
private static void scrcpy(Options options) throws IOException {
Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")");
final Device device = new Device(options);
List<CodecOption> codecOptions = CodecOption.parse(options.getCodecOptions());
Thread initThread = startInitThread(options);
boolean tunnelForward = options.isTunnelForward(); boolean tunnelForward = options.isTunnelForward();
@ -95,7 +82,6 @@ public final class Server {
// this is expected on close // this is expected on close
Ln.d("Screen streaming stopped"); Ln.d("Screen streaming stopped");
} finally { } finally {
initThread.interrupt();
if (controllerThread != null) { if (controllerThread != null) {
controllerThread.interrupt(); controllerThread.interrupt();
} }
@ -106,17 +92,6 @@ public final class Server {
} }
} }
private static Thread startInitThread(final Options options) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
initAndCleanUp(options);
}
});
thread.start();
return thread;
}
private static Thread startController(final Controller controller) { private static Thread startController(final Controller controller) {
Thread thread = new Thread(new Runnable() { Thread thread = new Thread(new Runnable() {
@Override @Override

View File

@ -1,84 +0,0 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ContentProvider;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.os.Build;
import java.io.IOException;
public class Settings {
public static final String TABLE_SYSTEM = ContentProvider.TABLE_SYSTEM;
public static final String TABLE_SECURE = ContentProvider.TABLE_SECURE;
public static final String TABLE_GLOBAL = ContentProvider.TABLE_GLOBAL;
private final ServiceManager serviceManager;
public Settings(ServiceManager serviceManager) {
this.serviceManager = serviceManager;
}
private static void execSettingsPut(String table, String key, String value) throws SettingsException {
try {
Command.exec("settings", "put", table, key, value);
} catch (IOException | InterruptedException e) {
throw new SettingsException("put", table, key, value, e);
}
}
private static String execSettingsGet(String table, String key) throws SettingsException {
try {
return Command.execReadLine("settings", "get", table, key);
} catch (IOException | InterruptedException e) {
throw new SettingsException("get", table, key, null, e);
}
}
public String getValue(String table, String key) throws SettingsException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) {
return provider.getValue(table, key);
} catch (SettingsException e) {
Ln.w("Could not get settings value via ContentProvider, fallback to settings process", e);
}
}
return execSettingsGet(table, key);
}
public void putValue(String table, String key, String value) throws SettingsException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) {
provider.putValue(table, key, value);
} catch (SettingsException e) {
Ln.w("Could not put settings value via ContentProvider, fallback to settings process", e);
}
}
execSettingsPut(table, key, value);
}
public String getAndPutValue(String table, String key, String value) throws SettingsException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) {
String oldValue = provider.getValue(table, key);
if (!value.equals(oldValue)) {
provider.putValue(table, key, value);
}
return oldValue;
} catch (SettingsException e) {
Ln.w("Could not get and put settings value via ContentProvider, fallback to settings process", e);
}
}
String oldValue = getValue(table, key);
if (!value.equals(oldValue)) {
putValue(table, key, value);
}
return oldValue;
}
}

Some files were not shown because too many files have changed in this diff Show More