Compare commits

..
5 Commits
Author SHA1 Message Date
Romain Vimont c7e46583b8 Improve process API
Prefix symbols and constants names and improve documentation.
2021-11-11 18:08:47 +01:00
Romain Vimont 848db078a5 Factorize common impl of process_execute()
Both implementations are the same. Move them to the common process.c.
2021-11-11 18:08:41 +01:00
Romain Vimont b197372dd3 Remove duplicate function declaration
The function process_terminate() was declared twice.
2021-11-11 18:08:41 +01:00
Romain Vimont 66f21cf680 Improve file API
Prefix symbols and constants names and improve documentation.
2021-11-11 18:08:35 +01:00
Romain Vimont f197c1181e Move functions from process to file
Move filesystem-related functions from process.[ch] to file.[ch].
2021-11-11 16:27:12 +01:00
58 changed files with 1019 additions and 2615 deletions
+3 -3
View File
@@ -270,10 +270,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:
-18
View File
@@ -140,24 +140,6 @@ Potresti anche dover configurare il [comportamento di ridimensionamento][scaling
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723 [scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
### Problema con Wayland
Per impostazione predefinita, SDL utilizza x11 su Linux. Il [video driver] può essere cambiato attraversio la variabile d'ambiente `SDL_VIDEODRIVER`:
[video driver]: https://wiki.libsdl.org/FAQUsingSDL#how_do_i_choose_a_specific_video_driver
```bash
export SDL_VIDEODRIVER=wayland
scrcpy
```
Su alcune distribuzioni (almeno Fedora), il pacchetto `libdecor` deve essere installato manualmente.
Vedi le issues [#2554] e [#2559].
[#2554]: https://github.com/Genymobile/scrcpy/issues/2554
[#2559]: https://github.com/Genymobile/scrcpy/issues/2559
### Crash del compositore KWin ### Crash del compositore KWin
+1 -22
View File
@@ -222,27 +222,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
@@ -283,6 +262,6 @@ to add some arguments.
This FAQ is available in other languages: This FAQ is available in other languages:
- [Italiano (Italiano, `it`) - v1.19](FAQ.it.md) - [Italiano (Italiano, `it`) - v1.17](FAQ.it.md)
- [한국어 (Korean, `ko`) - v1.11](FAQ.ko.md) - [한국어 (Korean, `ko`) - v1.11](FAQ.ko.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.18](FAQ.zh-Hans.md) - [简体中文 (Simplified Chinese, `zh-Hans`) - v1.18](FAQ.zh-Hans.md)
+25 -96
View File
@@ -1,6 +1,6 @@
_Apri il [README](README.md) originale e sempre aggiornato._ _Apri il [README](README.md) originale e sempre aggiornato._
# scrcpy (v1.19) # scrcpy (v1.17)
Questa applicazione fornisce la visualizzazione e il controllo dei dispositivi Android collegati via USB (o [via TCP/IP][article-tcpip]). Non richiede alcun accesso _root_. Questa applicazione fornisce la visualizzazione e il controllo dei dispositivi Android collegati via USB (o [via TCP/IP][article-tcpip]). Non richiede alcun accesso _root_.
Funziona su _GNU/Linux_, _Windows_ e _macOS_. Funziona su _GNU/Linux_, _Windows_ e _macOS_.
@@ -205,11 +205,10 @@ Se anche `--max-size` è specificata, il ridimensionamento è applicato dopo il
Per bloccare l'orientamento della trasmissione: Per bloccare l'orientamento della trasmissione:
```bash ```bash
scrcpy --lock-video-orientation # orientamento iniziale (corrente) scrcpy --lock-video-orientation 0 # orientamento naturale
scrcpy --lock-video-orientation=0 # orientamento naturale scrcpy --lock-video-orientation 1 # 90° antiorario
scrcpy --lock-video-orientation=1 # 90° antiorario scrcpy --lock-video-orientation 2 # 180°
scrcpy --lock-video-orientation=2 # 180° scrcpy --lock-video-orientation 3 # 90° orario
scrcpy --lock-video-orientation=3 # 90° orario
``` ```
Questo influisce sull'orientamento della registrazione. Questo influisce sull'orientamento della registrazione.
@@ -232,9 +231,7 @@ Per elencare i codificatori disponibili puoi immettere un nome di codificatore n
scrcpy --encoder _ scrcpy --encoder _
``` ```
### Cattura ### Registrazione
#### Registrazione
È possibile registrare lo schermo durante la trasmissione: È possibile registrare lo schermo durante la trasmissione:
@@ -256,75 +253,6 @@ I "fotogrammi saltati" sono registrati nonostante non siano mostrati in tempo re
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation [packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
#### v4l2loopback
Su Linux è possibile inviare il flusso video ad un dispositivo v4l2 loopback, cosicchè un dispositivo Android possa essere aperto come una webcam da qualsiasi strumento compatibile con v4l2.
Il modulo `v4l2loopback` deve essere installato:
```bash
sudo apt install v4l2loopback-dkms
```
Per creare un dispositvo v4l2:
```bash
sudo modprobe v4l2loopback
```
Questo creerà un nuovo dispositivo video in `/dev/videoN` dove `N` è un intero (più [opzioni](https://github.com/umlaeute/v4l2loopback#options) sono disponibili per crere più dispositivi o dispositivi con ID specifici).
Per elencare i dispositvi attivati:
```bash
# necessita del pacchetto v4l-utils
v4l2-ctl --list-devices
# semplice ma potrebbe essere sufficiente
ls /dev/video*
```
Per avviare scrcpy utilizzando un v4l2 sink:
```bash
scrcpy --v4l2-sink=/dev/videoN
scrcpy --v4l2-sink=/dev/videoN --no-display # disabilita la finestra di trasmissione
scrcpy --v4l2-sink=/dev/videoN -N # versione corta
```
(sostituisci `N` con l'ID del dispositivo, controlla con `ls /dev/video*`)
Una volta abilitato, puoi aprire il tuo flusso video con uno strumento compatibile con v4l2:
```bash
ffplay -i /dev/videoN
vlc v4l2:///dev/videoN # VLC potrebbe aggiungere del ritardo per il buffer
```
Per esempio potresti catturare il video in [OBS].
[OBS]: https://obsproject.com/
#### Buffering
È possibile aggiungere del buffer. Questo aumenta la latenza ma riduce il jitter (vedi [#2464]).
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
L'opzione è disponibile per il buffer della visualizzazione:
```bash
scrcpy --display-buffer=50 # aggiungi 50 ms di buffer per la visualizzazione
```
e per il V4L2 sink:
```bash
scrcpy --v4l2-buffer=500 # aggiungi 50 ms di buffer per il v4l2 sink
```
### Connessione ### Connessione
#### Wireless #### Wireless
@@ -551,6 +479,15 @@ scrcpy --turn-screen-off --stay-awake
scrcpy -Sw scrcpy -Sw
``` ```
#### Renderizzare i fotogrammi scaduti
Per minimizzare la latenza _scrcpy_ renderizza sempre l'ultimo fotogramma decodificato disponibile in maniera predefinita e tralascia quelli precedenti.
Per forzare la renderizzazione di tutti i fotogrammi (a costo di una possibile latenza superiore), utilizzare:
```bash
scrcpy --render-expired-frames
```
#### Mostrare i tocchi #### Mostrare i tocchi
@@ -670,14 +607,14 @@ Non c'è alcuna risposta visiva, un log è stampato nella console.
#### Trasferimento di file verso il dispositivo #### Trasferimento di file verso il dispositivo
Per trasferire un file in `/sdcard/Download` del dispositivo trascina e rilascia un file (non APK) nella finestra di _scrcpy_. Per trasferire un file in `/sdcard/` del dispositivo trascina e rilascia un file (non APK) nella finestra di _scrcpy_.
Non c'è alcuna risposta visiva, un log è stampato nella console. Non c'è alcuna risposta visiva, un log è stampato nella console.
La cartella di destinazione può essere cambiata all'avvio: La cartella di destinazione può essere cambiata all'avvio:
```bash ```bash
scrcpy --push-target=/sdcard/Movies/ scrcpy --push-target=/sdcard/Download/
``` ```
@@ -716,10 +653,10 @@ _<kbd>[Super]</kbd> è il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kbd>._
| Rotazione schermo a sinistra | <kbd>MOD</kbd>+<kbd>←</kbd> _(sinistra)_ | Rotazione schermo a sinistra | <kbd>MOD</kbd>+<kbd>←</kbd> _(sinistra)_
| Rotazione schermo a destra | <kbd>MOD</kbd>+<kbd>→</kbd> _(destra)_ | Rotazione schermo a destra | <kbd>MOD</kbd>+<kbd>→</kbd> _(destra)_
| Ridimensiona finestra a 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd> | Ridimensiona finestra a 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
| Ridimensiona la finestra per rimuovere i bordi neri | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Doppio click sinistro¹_ | Ridimensiona la finestra per rimuovere i bordi neri | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Doppio click¹_
| Premi il tasto `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Click centrale_ | Premi il tasto `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Click centrale_
| Premi il tasto `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Click destro²_ | Premi il tasto `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Click destro²_
| Premi il tasto `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4° click³_ | Premi il tasto `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd>
| Premi il tasto `MENU` (sblocca lo schermo) | <kbd>MOD</kbd>+<kbd>m</kbd> | Premi il tasto `MENU` (sblocca lo schermo) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Premi il tasto `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(su)_ | Premi il tasto `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(su)_
| Premi il tasto `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(giù)_ | Premi il tasto `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(giù)_
@@ -728,26 +665,18 @@ _<kbd>[Super]</kbd> è il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kbd>._
| Spegni lo schermo del dispositivo (continua a trasmettere) | <kbd>MOD</kbd>+<kbd>o</kbd> | Spegni lo schermo del dispositivo (continua a trasmettere) | <kbd>MOD</kbd>+<kbd>o</kbd>
| Accendi lo schermo del dispositivo | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd> | Accendi lo schermo del dispositivo | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| Ruota lo schermo del dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd> | Ruota lo schermo del dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
| Espandi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5° click³_ | Espandi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>n</kbd>
| Espandi il pannello delle impostazioni | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Doppio 5° click³_ | Chiudi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Chiudi pannelli | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd> | Copia negli appunti³ | <kbd>MOD</kbd>+<kbd>c</kbd>
| Copia negli appunti⁴ | <kbd>MOD</kbd>+<kbd>c</kbd> | Taglia negli appunti³ | <kbd>MOD</kbd>+<kbd>x</kbd>
| Taglia negli appunti⁴ | <kbd>MOD</kbd>+<kbd>x</kbd> | Sincronizza gli appunti e incolla³ | <kbd>MOD</kbd>+<kbd>v</kbd>
| Sincronizza gli appunti e incolla⁴ | <kbd>MOD</kbd>+<kbd>v</kbd>
| Inietta il testo degli appunti del computer | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> | Inietta il testo degli appunti del computer | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Abilita/Disabilita il contatore FPS (su stdout) | <kbd>MOD</kbd>+<kbd>i</kbd> | Abilita/Disabilita il contatore FPS (su stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pizzica per zoomare | <kbd>Ctrl</kbd>+_click e trascina_ | Pizzica per zoomare | <kbd>Ctrl</kbd>+_click e trascina_
_¹Doppio click sui bordi neri per rimuoverli._ _¹Doppio click sui bordi neri per rimuoverli._
_²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._ _²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._
_³4° e 5° pulsante del mouse, se il tuo mouse ne dispone._ _³Solo in Android >= 7._
_⁴Solo in Android >= 7._
Le scorciatoie con pulsanti ripetuti sono eseguite rilasciando e premendo il pulsante una seconda volta. Per esempio, per eseguire "Espandi il pannello delle impostazioni":
1. Premi e tieni premuto <kbd>MOD</kbd>.
2. Poi premi due volte <kbd>n</kbd>.
3. Infine rilascia <kbd>MOD</kbd>.
Tutte le scorciatoie <kbd>Ctrl</kbd>+_tasto_ sono inoltrate al dispositivo, così sono gestite dall'applicazione attiva. Tutte le scorciatoie <kbd>Ctrl</kbd>+_tasto_ sono inoltrate al dispositivo, così sono gestite dall'applicazione attiva.
+29 -103
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`で構成します:
+26 -86
View File
@@ -1,37 +1,26 @@
# scrcpy (v1.20) # scrcpy (v1.19)
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" /> ![icon](data/icon.png)
[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 +90,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]:
@@ -337,9 +326,7 @@ For example, you could capture the video within [OBS].
#### Buffering #### Buffering
It is possible to add buffering. This increases latency but reduces jitter (see It is possible to add buffering. This increases latency but reduces jitter (see
[#2464]). #2464).
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
The option is available for display buffering: The option is available for display buffering:
@@ -412,66 +399,21 @@ autoadb scrcpy -s '{}'
[AutoAdb]: https://github.com/rom1v/autoadb [AutoAdb]: https://github.com/rom1v/autoadb
#### Tunnels #### SSH tunnel
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):
##### Remote ADB server
To connect to a remote ADB server, make the server listen on all interfaces:
```bash ```bash
adb kill-server adb kill-server # kill the local adb server on 5037
adb -a nodaemon server start ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
# keep this open # keep this open
``` ```
**Warning: all communications between clients and ADB server are unencrypted.** From another terminal:
Suppose that this server is accessible at 192.168.1.2. Then, from another
terminal, run scrcpy:
```bash ```bash
export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037
scrcpy --tunnel-host=192.168.1.2
```
By default, scrcpy uses the local port used for `adb forward` tunnel
establishment (typically `27183`, see `--port`). It is also possible to force a
different tunnel port (it may be useful in more complex situations, when more
redirections are involved):
```
scrcpy --tunnel-port=1234
```
##### SSH tunnel
To communicate with a remote ADB server securely, it is preferable to use a SSH
tunnel.
First, make sure the ADB server is running on the remote computer:
```bash
adb start-server
```
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
```
From another terminal, run scrcpy:
```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy scrcpy
``` ```
@@ -479,16 +421,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
``` ```
@@ -746,7 +686,7 @@ a location inverted through the center of the screen.
By default, scrcpy uses Android key or text injection: it works everywhere, but By default, scrcpy uses Android key or text injection: it works everywhere, but
is limited to ASCII. is limited to ASCII.
On Linux, scrcpy can simulate a physical USB keyboard on Android to provide a On Linux, scrcpy can simulate a USB physical keyboard on Android to provide a
better input experience (using [USB HID over AOAv2][hid-aoav2]): the virtual better input experience (using [USB HID over AOAv2][hid-aoav2]): the virtual
keyboard is disabled and it works for all characters and IME. keyboard is disabled and it works for all characters and IME.
@@ -997,10 +937,10 @@ Read the [developers page].
This README is available in other languages: 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.17](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.17](README.pt-br.md)
- [Español (Spanish, `sp`) - v1.17](README.sp.md) - [Español (Spanish, `sp`) - v1.17](README.sp.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.17](README.zh-Hans.md) - [简体中文 (Simplified Chinese, `zh-Hans`) - v1.17](README.zh-Hans.md)
- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md) - [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md)
+43 -131
View File
@@ -1,6 +1,6 @@
_Apenas o [README](README.md) original é garantido estar atualizado._ _Apenas o [README](README.md) original é garantido estar atualizado._
# scrcpy (v1.19) # scrcpy (v1.17)
Esta aplicação fornece exibição e controle de dispositivos Android conectados via Esta aplicação fornece exibição e controle de dispositivos Android conectados via
USB (ou [via TCP/IP][article-tcpip]). Não requer nenhum acesso _root_. USB (ou [via TCP/IP][article-tcpip]). Não requer nenhum acesso _root_.
@@ -38,18 +38,6 @@ controlá-lo usando teclado e mouse.
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a> <a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
### Sumário
- Linux: `apt install scrcpy`
- Windows: [baixar][direct-win64]
- macOS: `brew install scrcpy`
Compilar pelos arquivos fontes: [BUILD] ([processo simplificado][BUILD_simple])
[BUILD]: BUILD.md
[BUILD_simple]: BUILD.md#simple
### Linux ### Linux
No Debian (_testing_ e _sid_ por enquanto) e Ubuntu (20.04): No Debian (_testing_ e _sid_ por enquanto) e Ubuntu (20.04):
@@ -79,7 +67,9 @@ Para Gentoo, uma [Ebuild] está disponível: [`scrcpy/`][ebuild-link].
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild [Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy [ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
Você também pode [compilar o app manualmente][BUILD] ([processo simplificado][BUILD_simple]). Você também pode [compilar o app manualmente][BUILD] (não se preocupe, não é tão
difícil).
### Windows ### Windows
@@ -123,18 +113,13 @@ brew install scrcpy
Você precisa do `adb`, acessível pelo seu `PATH`. Se você ainda não o tem: Você precisa do `adb`, acessível pelo seu `PATH`. Se você ainda não o tem:
```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
``` ```
Está também disponivel em [MacPorts], que prepara o adb para você:
```bash
sudo port install scrcpy
```
[MacPorts]: https://www.macports.org/
Você também pode [compilar o app manualmente][BUILD]. Você também pode [compilar o app manualmente][BUILD].
@@ -210,11 +195,10 @@ Se `--max-size` também for especificado, o redimensionamento é aplicado após
Para travar a orientação do espelhamento: Para travar a orientação do espelhamento:
```bash ```bash
scrcpy --lock-video-orientation # orientação inicial (Atual) scrcpy --lock-video-orientation 0 # orientação natural
scrcpy --lock-video-orientation=0 # orientação natural scrcpy --lock-video-orientation 1 # 90° sentido anti-horário
scrcpy --lock-video-orientation=1 # 90° sentido anti-horário scrcpy --lock-video-orientation 2 # 180°
scrcpy --lock-video-orientation=2 # 180° scrcpy --lock-video-orientation 3 # 90° sentido horário
scrcpy --lock-video-orientation=3 # 90° sentido horário
``` ```
Isso afeta a orientação de gravação. Isso afeta a orientação de gravação.
@@ -238,9 +222,7 @@ erro dará os encoders disponíveis:
scrcpy --encoder _ scrcpy --encoder _
``` ```
### Captura ### Gravando
#### Gravando
É possível gravar a tela enquanto ocorre o espelhamento: É possível gravar a tela enquanto ocorre o espelhamento:
@@ -264,79 +246,6 @@ pacotes][packet delay variation] não impacta o arquivo gravado.
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation [packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
#### v4l2loopback
Em Linux, é possível enviar a transmissão do video para um disposiivo v4l2 loopback, assim
o dispositivo Android pode ser aberto como uma webcam por qualquer ferramneta capaz de v4l2
The module `v4l2loopback` must be installed:
```bash
sudo apt install v4l2loopback-dkms
```
Para criar um dispositivo v4l2:
```bash
sudo modprobe v4l2loopback
```
Isso criara um novo dispositivo de vídeo em `/dev/videoN`, onde `N` é uma integer
(mais [opções](https://github.com/umlaeute/v4l2loopback#options) estão disponiveis
para criar varios dispositivos ou dispositivos com IDs específicas).
Para listar os dispositivos disponíveis:
```bash
# requer o pacote v4l-utils
v4l2-ctl --list-devices
# simples, mas pode ser suficiente
ls /dev/video*
```
Para iniciar o scrcpy usando o coletor v4l2 (sink):
```bash
scrcpy --v4l2-sink=/dev/videoN
scrcpy --v4l2-sink=/dev/videoN --no-display # desativa a janela espelhada
scrcpy --v4l2-sink=/dev/videoN -N # versão curta
```
(troque `N` pelo ID do dipositivo, verifique com `ls /dev/video*`)
Uma vez ativado, você pode abrir suas trasmissões de videos com uma ferramenta capaz de v4l2:
```bash
ffplay -i /dev/videoN
vlc v4l2:///dev/videoN # VLC pode adicionar um pouco de atraso de buffering
```
Por exemplo, você pode capturar o video dentro do [OBS].
[OBS]: https://obsproject.com/
#### Buffering
É possivel adicionar buffering. Isso aumenta a latência, mas reduz a tenção (jitter) (veja
[#2464]).
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
A opção éta disponivel para buffering de exibição:
```bash
scrcpy --display-buffer=50 # adiciona 50 ms de buffering para a exibição
```
e coletor V4L2:
```bash
scrcpy --v4l2-buffer=500 # adiciona 500 ms de buffering para coletor V4L2
```
,
### Conexão ### Conexão
#### Sem fio #### Sem fio
@@ -579,6 +488,18 @@ scrcpy -Sw
``` ```
#### Renderizar frames expirados
Por padrão, para minimizar a latência, _scrcpy_ sempre renderiza o último frame decodificado
disponível, e descarta o anterior.
Para forçar a renderização de todos os frames (com o custo de um possível aumento de
latência), use:
```bash
scrcpy --render-expired-frames
```
#### Mostrar toques #### Mostrar toques
Para apresentações, pode ser útil mostrar toques físicos (no dispositivo Para apresentações, pode ser útil mostrar toques físicos (no dispositivo
@@ -726,7 +647,7 @@ Não existe feedback visual, um log é imprimido no console.
#### Enviar arquivo para dispositivo #### Enviar arquivo para dispositivo
Para enviar um arquivo para `/sdcard/Download/` no dispositivo, arraste e solte um arquivo (não-APK) para a Para enviar um arquivo para `/sdcard/` no dispositivo, arraste e solte um arquivo (não-APK) para a
janela do _scrcpy_. janela do _scrcpy_.
Não existe feedback visual, um log é imprimido no console. Não existe feedback visual, um log é imprimido no console.
@@ -773,12 +694,12 @@ _<kbd>[Super]</kbd> é tipicamente a tecla <kbd>Windows</kbd> ou <kbd>Cmd</kbd>.
| Mudar modo de tela cheia | <kbd>MOD</kbd>+<kbd>f</kbd> | Mudar modo de tela cheia | <kbd>MOD</kbd>+<kbd>f</kbd>
| Rotacionar display para esquerda | <kbd>MOD</kbd>+<kbd>←</kbd> _(esquerda)_ | Rotacionar display para esquerda | <kbd>MOD</kbd>+<kbd>←</kbd> _(esquerda)_
| Rotacionar display para direita | <kbd>MOD</kbd>+<kbd>→</kbd> _(direita)_ | Rotacionar display para direita | <kbd>MOD</kbd>+<kbd>→</kbd> _(direita)_
| Redimensionar janela para 1:1 (pixel-perfeito) | <kbd>MOD</kbd>+<kbd>g</kbd> | Redimensionar janela para 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
| Redimensionar janela para remover bordas pretas | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Clique-duplo-esquerdo¹_ | Redimensionar janela para remover bordas pretas | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Clique-duplo¹_
| Clicar em `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Clique-do-meio_ | Clicar em `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Clique-do-meio_
| Clicar em `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Clique-direito²_ | Clicar em `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Clique-direito²_
| Clicar em `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _Clique-do-4.°³_ | Clicar em `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd>
| Clicar em `MENU` (desbloquear tela) | <kbd>MOD</kbd>+<kbd>m</kbd> | Clicar em `MENU` (desbloquear tela | <kbd>MOD</kbd>+<kbd>m</kbd>
| Clicar em `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(cima)_ | Clicar em `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(cima)_
| Clicar em `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(baixo)_ | Clicar em `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(baixo)_
| Clicar em `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd> | Clicar em `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
@@ -786,27 +707,18 @@ _<kbd>[Super]</kbd> é tipicamente a tecla <kbd>Windows</kbd> ou <kbd>Cmd</kbd>.
| Desligar tela do dispositivo (continuar espelhando) | <kbd>MOD</kbd>+<kbd>o</kbd> | Desligar tela do dispositivo (continuar espelhando) | <kbd>MOD</kbd>+<kbd>o</kbd>
| Ligar tela do dispositivo | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd> | Ligar tela do dispositivo | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| Rotacionar tela do dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd> | Rotacionar tela do dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
| Expandir painel de notificação | <kbd>MOD</kbd>+<kbd>n</kbd> \| _Clique-do-5.°³_ | Expandir painel de notificação | <kbd>MOD</kbd>+<kbd>n</kbd>
| Expandir painel de configurção | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Clique-duplo-do-5.°³_ | Colapsar painel de notificação | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Colapsar paineis | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd> | Copiar para área de transferência³ | <kbd>MOD</kbd>+<kbd>c</kbd>
| Copiar para área de transferência⁴ | <kbd>MOD</kbd>+<kbd>c</kbd> | Recortar para área de transferência³ | <kbd>MOD</kbd>+<kbd>x</kbd>
| Recortar para área de transferência⁴ | <kbd>MOD</kbd>+<kbd>x</kbd> | Sincronizar áreas de transferência e colar³ | <kbd>MOD</kbd>+<kbd>v</kbd>
| Sincronizar áreas de transferência e colar⁴ | <kbd>MOD</kbd>+<kbd>v</kbd>
| Injetar texto da área de transferência do computador | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> | Injetar texto da área de transferência do computador | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Ativar/desativar contador de FPS (em stdout) | <kbd>MOD</kbd>+<kbd>i</kbd> | Ativar/desativar contador de FPS (em stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pinçar para dar zoom | <kbd>Ctrl</kbd>+_Clicar-e-mover_ | Pinçar para dar zoom | <kbd>Ctrl</kbd>+_clicar-e-mover_
_¹Clique-duplo-esquerdo na borda preta para remove-la._ _¹Clique-duplo em bordas pretas para removê-las._
_²Clique-direito liga a tela caso esteja desligada, pressione BACK caso contrário._ _²Clique-direito liga a tela se ela estiver desligada, pressiona BACK caso contrário._
_³4.° and 5.° botões do mouse, caso o mouse possua._ _³Apenas em Android >= 7._
_⁴Apenas em Android >= 7._
Atalhos com teclas reptidas são executados soltando e precionando a tecla
uma segunda vez. Por exemplo, para executar "Expandir painel de Configurção":
1. Mantenha pressionado <kbd>MOD</kbd>.
2. Depois click duas vezes <kbd>n</kbd>.
3. Finalmente, solte <kbd>MOD</kbd>.
Todos os atalhos <kbd>Ctrl</kbd>+_tecla_ são encaminhados para o dispositivo, para que eles sejam Todos os atalhos <kbd>Ctrl</kbd>+_tecla_ são encaminhados para o dispositivo, para que eles sejam
tratados pela aplicação ativa. tratados pela aplicação ativa.
@@ -817,9 +729,7 @@ tratados pela aplicação ativa.
Para usar um binário _adb_ específico, configure seu caminho na variável de ambiente Para usar um binário _adb_ específico, configure seu caminho na variável de ambiente
`ADB`: `ADB`:
```bash ADB=/caminho/para/adb scrcpy
ADB=/caminho/para/adb scrcpy
```
Para sobrepor o caminho do arquivo `scrcpy-server`, configure seu caminho em Para sobrepor o caminho do arquivo `scrcpy-server`, configure seu caminho em
`SCRCPY_SERVER_PATH`. `SCRCPY_SERVER_PATH`.
@@ -841,6 +751,8 @@ Um colega me desafiou a encontrar um nome tão impronunciável quanto [gnirehtet
Veja [BUILD]. Veja [BUILD].
[BUILD]: BUILD.md
## Problemas comuns ## Problemas comuns
+6 -11
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',
@@ -26,14 +25,11 @@ src = [
'src/stream.c', 'src/stream.c',
'src/video_buffer.c', 'src/video_buffer.c',
'src/util/file.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/strbuf.c', 'src/util/strbuf.c',
'src/util/str.c', 'src/util/str_util.c',
'src/util/term.c', 'src/util/term.c',
'src/util/thread.c', 'src/util/thread.c',
'src/util/tick.c', 'src/util/tick.c',
@@ -199,9 +195,8 @@ if get_option('buildtype') == 'debug'
'tests/test_cli.c', 'tests/test_cli.c',
'src/cli.c', 'src/cli.c',
'src/options.c', 'src/options.c',
'src/util/net.c',
'src/util/str.c',
'src/util/strbuf.c', 'src/util/strbuf.c',
'src/util/str_util.c',
'src/util/term.c', 'src/util/term.c',
]], ]],
['test_clock', [ ['test_clock', [
@@ -211,8 +206,8 @@ 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/strbuf.c', 'src/util/strbuf.c',
'src/util/str_util.c',
]], ]],
['test_device_msg_deserialize', [ ['test_device_msg_deserialize', [
'tests/test_device_msg_deserialize.c', 'tests/test_device_msg_deserialize.c',
@@ -225,10 +220,10 @@ if get_option('buildtype') == 'debug'
'tests/test_strbuf.c', 'tests/test_strbuf.c',
'src/util/strbuf.c', 'src/util/strbuf.c',
]], ]],
['test_str', [ ['test_strutil', [
'tests/test_str.c', 'tests/test_strutil.c',
'src/util/str.c',
'src/util/strbuf.c', 'src/util/strbuf.c',
'src/util/str_util.c',
]], ]],
] ]
-12
View File
@@ -203,18 +203,6 @@ Enable "show touches" on start, restore the initial value on exit.
It only shows physical touches (not clicks from scrcpy). It only shows physical touches (not clicks from scrcpy).
.TP
.BI "\-\-tunnel\-host " ip
Set the IP address of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
Default is localhost.
.TP
.BI "\-\-tunnel\-port " port
Set the TCP port of the adb tunnel to reach the scrcpy server. This option automatically enables --force-adb-forward.
Default is 0 (not forced): the local port used for establishing the tunnel will be used.
.TP .TP
.BI "\-\-v4l2-sink " /dev/videoN .BI "\-\-v4l2-sink " /dev/videoN
Output to v4l2loopback device. Output to v4l2loopback device.
+52 -83
View File
@@ -7,8 +7,7 @@
#include "util/file.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;
@@ -110,9 +109,9 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) {
free(buf); free(buf);
} }
static sc_pid sc_pid
adb_execute_p(const char *serial, const char *const adb_cmd[], adb_execute_p(const char *serial, const char *const adb_cmd[],
size_t len, sc_pipe *pout) { size_t len, sc_pipe *pin, sc_pipe *pout, sc_pipe *perr) {
int i; int i;
sc_pid pid; sc_pid pid;
@@ -133,7 +132,7 @@ 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 sc_process_result r =
sc_process_execute_p(argv, &pid, NULL, pout, NULL); sc_process_execute_p(argv, &pid, pin, pout, perr);
if (r != SC_PROCESS_SUCCESS) { if (r != SC_PROCESS_SUCCESS) {
show_adb_err_msg(r, argv); show_adb_err_msg(r, argv);
pid = SC_PROCESS_NONE; pid = SC_PROCESS_NONE;
@@ -145,12 +144,12 @@ adb_execute_p(const char *serial, const char *const adb_cmd[],
sc_pid sc_pid
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) {
return adb_execute_p(serial, adb_cmd, len, NULL); return adb_execute_p(serial, adb_cmd, len, NULL, NULL, NULL);
} }
static sc_pid sc_pid
adb_exec_forward(const char *serial, uint16_t local_port, adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name) { 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);
@@ -159,17 +158,17 @@ adb_exec_forward(const char *serial, uint16_t local_port,
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
static sc_pid sc_pid
adb_exec_forward_remove(const char *serial, uint16_t local_port) { adb_forward_remove(const char *serial, uint16_t local_port) {
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)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
static sc_pid sc_pid
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) { 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);
@@ -178,24 +177,24 @@ adb_exec_reverse(const char *serial, const char *device_socket_name,
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
static sc_pid sc_pid
adb_exec_reverse_remove(const char *serial, const char *device_socket_name) { adb_reverse_remove(const char *serial, const char *device_socket_name) {
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)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
static sc_pid sc_pid
adb_exec_push(const char *serial, const char *local, const char *remote) { adb_push(const char *serial, const char *local, const char *remote) {
#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 SC_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 SC_PROCESS_NONE;
@@ -213,12 +212,12 @@ adb_exec_push(const char *serial, const char *local, const char *remote) {
return pid; return pid;
} }
static sc_pid sc_pid
adb_exec_install(const char *serial, const char *local) { 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 SC_PROCESS_NONE;
} }
@@ -234,75 +233,45 @@ adb_exec_install(const char *serial, const char *local) {
return pid; return pid;
} }
static sc_pid static ssize_t
adb_exec_get_serialno(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), pout); const char *name) {
sc_pipe pout;
sc_pid pid = adb_execute_p(serial, adb_cmd, adb_cmd_len, NULL, &pout, NULL);
ssize_t r = sc_pipe_read_all(pout, buf, buf_len);
sc_pipe_close(pout);
if (!sc_process_check_success(pid, name, true)) {
return -1;
}
return r;
} }
bool static size_t
adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, truncate_first_line(char *data, size_t len) {
const char *device_socket_name) { data[len - 1] = '\0';
sc_pid pid = adb_exec_forward(serial, local_port, device_socket_name); char *eol = strpbrk(data, "\r\n");
return sc_process_check_success_intr(intr, pid, "adb forward", true); if (eol) {
} *eol = '\0';
len = eol - data;
bool }
adb_forward_remove(struct sc_intr *intr, const char *serial, return len;
uint16_t local_port) {
sc_pid pid = adb_exec_forward_remove(serial, local_port);
return sc_process_check_success_intr(intr, pid, "adb forward --remove",
true);
}
bool
adb_reverse(struct sc_intr *intr, const char *serial,
const char *device_socket_name, uint16_t local_port) {
sc_pid pid = adb_exec_reverse(serial, device_socket_name, local_port);
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) {
sc_pid pid = adb_exec_reverse_remove(serial, device_socket_name);
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) {
sc_pid pid = adb_exec_push(serial, local, remote);
return sc_process_check_success_intr(intr, pid, "adb push", true);
}
bool
adb_install(struct sc_intr *intr, const char *serial, const char *local) {
sc_pid pid = adb_exec_install(serial, local);
return sc_process_check_success_intr(intr, pid, "adb install", true);
} }
char * char *
adb_get_serialno(struct sc_intr *intr) { adb_get_serialno(void) {
sc_pipe pout;
sc_pid pid = adb_exec_get_serialno(&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);
} }
+20 -23
View File
@@ -6,40 +6,37 @@
#include <stdbool.h> #include <stdbool.h>
#include <inttypes.h> #include <inttypes.h>
#include "util/intr.h" #include "util/process.h"
sc_pid sc_pid
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);
bool sc_pid
adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, adb_execute_p(const char *serial, const char *const adb_cmd[],
size_t len, sc_pipe *pin, sc_pipe *pout, sc_pipe *perr);
sc_pid
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name); const char *device_socket_name);
bool sc_pid
adb_forward_remove(struct sc_intr *intr, const char *serial, adb_forward_remove(const char *serial, uint16_t local_port);
uint16_t local_port);
bool sc_pid
adb_reverse(struct sc_intr *intr, const char *serial, adb_reverse(const char *serial, const char *device_socket_name,
const char *device_socket_name, uint16_t local_port); uint16_t local_port);
bool sc_pid
adb_reverse_remove(struct sc_intr *intr, const char *serial, adb_reverse_remove(const char *serial, const char *device_socket_name);
const char *device_socket_name);
bool sc_pid
adb_push(struct sc_intr *intr, const char *serial, const char *local, adb_push(const char *serial, const char *local, const char *remote);
const char *remote);
bool sc_pid
adb_install(struct sc_intr *intr, const char *serial, const char *local); adb_install(const char *serial, const char *local);
/** // Return the result of "adb get-serialno".
* Execute `adb get-serialno`
*
* Return the result, to be freed by the caller, or NULL on error.
*/
char * char *
adb_get_serialno(struct sc_intr *intr); adb_get_serialno(void);
#endif #endif
-165
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)) {
// 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)) {
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)) {
// 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);
} else {
ret = adb_reverse_remove(intr, serial, SC_SOCKET_NAME);
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;
}
-47
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
+17 -74
View File
@@ -9,9 +9,8 @@
#include "options.h" #include "options.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h"
#include "util/str.h"
#include "util/strbuf.h" #include "util/strbuf.h"
#include "util/str_util.h"
#include "util/term.h" #include "util/term.h"
#define STR_IMPL_(x) #x #define STR_IMPL_(x) #x
@@ -47,8 +46,6 @@
#define OPT_V4L2_SINK 1027 #define OPT_V4L2_SINK 1027
#define OPT_DISPLAY_BUFFER 1028 #define OPT_DISPLAY_BUFFER 1028
#define OPT_V4L2_BUFFER 1029 #define OPT_V4L2_BUFFER 1029
#define OPT_TUNNEL_HOST 1030
#define OPT_TUNNEL_PORT 1031
struct sc_option { struct sc_option {
char shortopt; char shortopt;
@@ -333,25 +330,6 @@ static const struct sc_option options[] = {
"on exit.\n" "on exit.\n"
"It only shows physical touches (not clicks from scrcpy).", "It only shows physical touches (not clicks from scrcpy).",
}, },
{
.longopt_id = OPT_TUNNEL_HOST,
.longopt = "tunnel-host",
.argdesc = "ip",
.text = "Set the IP address of the adb tunnel to reach the scrcpy "
"server. This option automatically enables "
"--force-adb-forward.\n"
"Default is localhost.",
},
{
.longopt_id = OPT_TUNNEL_PORT,
.longopt = "tunnel-port",
.argdesc = "port",
.text = "Set the TCP port of the adb tunnel to reach the scrcpy "
"server. This option automatically enables "
"--force-adb-forward.\n"
"Default is 0 (not forced): the local port used for "
"establishing the tunnel will be used.",
},
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
{ {
.longopt_id = OPT_V4L2_SINK, .longopt_id = OPT_V4L2_SINK,
@@ -591,10 +569,6 @@ sc_getopt_adapter_create_longopts(void) {
size_t out_idx = 0; size_t out_idx = 0;
for (size_t i = 0; i < ARRAY_LEN(options); ++i) { for (size_t i = 0; i < ARRAY_LEN(options); ++i) {
const struct sc_option *in = &options[i]; const struct sc_option *in = &options[i];
// If longopt_id is set, then longopt must be set
assert(!in->longopt_id || in->longopt);
if (!in->longopt) { if (!in->longopt) {
// The longopts array must only contain long options // The longopts array must only contain long options
continue; continue;
@@ -697,12 +671,12 @@ print_option_usage_header(const struct sc_option *opt) {
} }
} }
printf("\n %s\n", buf.s); fprintf(stderr, "\n %s\n", buf.s);
free(buf.s); free(buf.s);
return; return;
error: error:
printf("<ERROR>\n"); fprintf(stderr, "<ERROR>\n");
} }
static void static void
@@ -718,11 +692,11 @@ print_option_usage(const struct sc_option *opt, unsigned cols) {
char *text = sc_str_wrap_lines(opt->text, cols, 8); char *text = sc_str_wrap_lines(opt->text, cols, 8);
if (!text) { if (!text) {
printf("<ERROR>\n"); fprintf(stderr, "<ERROR>\n");
return; return;
} }
printf("%s\n", text); fprintf(stderr, "%s\n", text);
free(text); free(text);
} }
@@ -733,11 +707,11 @@ print_shortcuts_intro(unsigned cols) {
"(left) Alt or (left) Super, but it can be configured by " "(left) Alt or (left) Super, but it can be configured by "
"--shortcut-mod (see above).", cols, 4); "--shortcut-mod (see above).", cols, 4);
if (!intro) { if (!intro) {
printf("<ERROR>\n"); fprintf(stderr, "<ERROR>\n");
return; return;
} }
printf("%s\n", intro); fprintf(stderr, "%s\n", intro);
free(intro); free(intro);
} }
@@ -747,21 +721,21 @@ print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) {
assert(shortcut->shortcuts[0]); // At least one shortcut assert(shortcut->shortcuts[0]); // At least one shortcut
assert(shortcut->text); assert(shortcut->text);
printf("\n"); fprintf(stderr, "\n");
unsigned i = 0; unsigned i = 0;
while (shortcut->shortcuts[i]) { while (shortcut->shortcuts[i]) {
printf(" %s\n", shortcut->shortcuts[i]); fprintf(stderr, " %s\n", shortcut->shortcuts[i]);
++i; ++i;
}; };
char *text = sc_str_wrap_lines(shortcut->text, cols, 8); char *text = sc_str_wrap_lines(shortcut->text, cols, 8);
if (!text) { if (!text) {
printf("<ERROR>\n"); fprintf(stderr, "<ERROR>\n");
return; return;
} }
printf("%s\n", text); fprintf(stderr, "%s\n", text);
free(text); free(text);
} }
@@ -785,14 +759,14 @@ scrcpy_print_usage(const char *arg0) {
} }
} }
printf("Usage: %s [options]\n\n" fprintf(stderr, "Usage: %s [options]\n\n"
"Options:\n", arg0); "Options:\n", arg0);
for (size_t i = 0; i < ARRAY_LEN(options); ++i) { for (size_t i = 0; i < ARRAY_LEN(options); ++i) {
print_option_usage(&options[i], cols); print_option_usage(&options[i], cols);
} }
// Print shortcuts section // Print shortcuts section
printf("\nShortcuts:\n\n"); fprintf(stderr, "\nShortcuts:\n\n");
print_shortcuts_intro(cols); print_shortcuts_intro(cols);
for (size_t i = 0; i < ARRAY_LEN(shortcuts); ++i) { for (size_t i = 0; i < ARRAY_LEN(shortcuts); ++i) {
print_shortcut(&shortcuts[i], cols); print_shortcut(&shortcuts[i], cols);
@@ -805,9 +779,9 @@ parse_integer_arg(const char *s, long *out, bool accept_suffix, long min,
long value; long value;
bool ok; bool ok;
if (accept_suffix) { if (accept_suffix) {
ok = sc_str_parse_integer_with_suffix(s, &value); ok = parse_integer_with_suffix(s, &value);
} else { } else {
ok = sc_str_parse_integer(s, &value); ok = parse_integer(s, &value);
} }
if (!ok) { if (!ok) {
LOGE("Could not parse %s: %s", name, s); LOGE("Could not parse %s: %s", name, s);
@@ -827,7 +801,7 @@ parse_integer_arg(const char *s, long *out, bool accept_suffix, long min,
static size_t static size_t
parse_integers_arg(const char *s, size_t max_items, long *out, long min, parse_integers_arg(const char *s, size_t max_items, long *out, long min,
long max, const char *name) { long max, const char *name) {
size_t count = sc_str_parse_integers(s, ':', max_items, out); size_t count = parse_integers(s, ':', max_items, out);
if (!count) { if (!count) {
LOGE("Could not parse %s: %s", name, s); LOGE("Could not parse %s: %s", name, s);
return 0; return 0;
@@ -1149,21 +1123,6 @@ parse_record_format(const char *optarg, enum sc_record_format *format) {
return false; return false;
} }
static bool
parse_ip(const char *optarg, uint32_t *ipv4) {
return net_parse_ipv4(optarg, ipv4);
}
static bool
parse_port(const char *optarg, uint16_t *port) {
long value;
if (!parse_integer_arg(optarg, &value, false, 0, 0xFFFF, "port")) {
return false;
}
*port = (uint16_t) value;
return true;
}
static enum sc_record_format static enum sc_record_format
guess_record_format(const char *filename) { guess_record_format(const char *filename) {
size_t len = strlen(filename); size_t len = strlen(filename);
@@ -1236,16 +1195,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false; return false;
} }
break; break;
case OPT_TUNNEL_HOST:
if (!parse_ip(optarg, &opts->tunnel_host)) {
return false;
}
break;
case OPT_TUNNEL_PORT:
if (!parse_port(optarg, &opts->tunnel_port)) {
return false;
}
break;
case 'n': case 'n':
opts->control = false; opts->control = false;
break; break;
@@ -1405,12 +1354,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
} }
#endif #endif
if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) {
LOGI("Tunnel host/port is set, "
"--force-adb-forward automatically enabled.");
opts->force_adb_forward = true;
}
int index = optind; int index = optind;
if (index < argc) { if (index < argc) {
LOGE("Unexpected additional argument: %s", argv[index]); LOGE("Unexpected additional argument: %s", argv[index]);
+2 -2
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
@@ -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;
+2 -4
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)
+52 -33
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) { file_handler->serial = strdup(serial);
LOGE("Could not create intr"); if (!file_handler->serial) {
sc_cond_destroy(&file_handler->event_cond); LOGW("Could not strdup serial");
sc_mutex_destroy(&file_handler->mutex); sc_cond_destroy(&file_handler->event_cond);
} sc_mutex_destroy(&file_handler->mutex);
return false;
file_handler->serial = strdup(serial); }
if (!file_handler->serial) { } else {
LOGE("Could not strdup serial"); file_handler->serial = NULL;
sc_intr_destroy(&file_handler->intr);
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
return false;
} }
// lazy initialization // lazy initialization
file_handler->initialized = false; file_handler->initialized = false;
file_handler->stopped = false; file_handler->stopped = false;
file_handler->current_process = SC_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 sc_pid
install_apk(const char *serial, const char *file) {
return adb_install(serial, file);
}
static sc_pid
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 = SC_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,26 +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;
sc_pid pid;
if (req.action == ACTION_INSTALL_APK) {
LOGI("Installing %s...", req.file);
pid = install_apk(file_handler->serial, req.file);
} else {
LOGI("Pushing %s...", req.file);
pid = push_file(file_handler->serial, req.file,
file_handler->push_target);
}
file_handler->current_process = pid;
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 (sc_process_check_success(pid, "adb install", false)) {
bool ok = adb_install(intr, serial, req.file);
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 (sc_process_check_success(pid, "adb push", false)) {
bool ok = adb_push(intr, serial, req.file, push_target); LOGI("%s successfully pushed to %s", req.file,
if (ok) { file_handler->push_target);
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 necessarily already terminated)
// Execute this call with mutex locked to avoid race conditions with
// file_handler_stop()
sc_process_close(file_handler->current_process);
file_handler->current_process = SC_PROCESS_NONE;
sc_mutex_unlock(&file_handler->mutex);
file_handler_request_destroy(&req); file_handler_request_destroy(&req);
} }
return 0; return 0;
@@ -168,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 != SC_PROCESS_NONE) {
if (!sc_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);
} }
+1 -3
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;
sc_pid current_process;
struct file_handler_request_queue queue; struct file_handler_request_queue queue;
struct sc_intr intr;
}; };
bool bool
+1 -1
View File
@@ -307,7 +307,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
// requested. Wait a bit so that the clipboard is set before // requested. Wait a bit so that the clipboard is set before
// injecting Ctrl+v via HID, otherwise it would paste the old // injecting Ctrl+v via HID, otherwise it would paste the old
// clipboard content. // clipboard content.
hid_event.delay = SC_TICK_FROM_MS(5); hid_event.delay = SC_TICK_FROM_MS(2);
} }
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) { if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
+2 -10
View File
@@ -10,7 +10,7 @@
#include "compat.h" #include "compat.h"
#include "util/file.h" #include "util/file.h"
#include "util/log.h" #include "util/log.h"
#include "util/str.h" #include "util/str_util.h"
#define SCRCPY_PORTABLE_ICON_FILENAME "icon.png" #define SCRCPY_PORTABLE_ICON_FILENAME "icon.png"
#define SCRCPY_DEFAULT_ICON_PATH \ #define SCRCPY_DEFAULT_ICON_PATH \
@@ -26,7 +26,7 @@ get_icon_path(void) {
if (icon_path_env) { if (icon_path_env) {
// if the envvar is set, use it // if the envvar is set, use it
#ifdef __WINDOWS__ #ifdef __WINDOWS__
char *icon_path = sc_str_from_wchars(icon_path_env); char *icon_path = utf8_from_wide_char(icon_path_env);
#else #else
char *icon_path = strdup(icon_path_env); char *icon_path = strdup(icon_path_env);
#endif #endif
@@ -158,12 +158,6 @@ free_ctx:
return result; 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 static SDL_PixelFormatEnum
to_sdl_pixel_format(enum AVPixelFormat fmt) { to_sdl_pixel_format(enum AVPixelFormat fmt) {
switch (fmt) { switch (fmt) {
@@ -178,9 +172,7 @@ to_sdl_pixel_format(enum AVPixelFormat fmt) {
case AV_PIX_FMT_BGR565BE: return SDL_PIXELFORMAT_BGR565; case AV_PIX_FMT_BGR565BE: return SDL_PIXELFORMAT_BGR565;
case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_BGR555; case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_BGR555;
case AV_PIX_FMT_RGB444BE: return SDL_PIXELFORMAT_RGB444; 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; case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_BGR444;
#endif
case AV_PIX_FMT_PAL8: return SDL_PIXELFORMAT_INDEX8; case AV_PIX_FMT_PAL8: return SDL_PIXELFORMAT_INDEX8;
default: return SDL_PIXELFORMAT_UNKNOWN; default: return SDL_PIXELFORMAT_UNKNOWN;
} }
+2 -3
View File
@@ -47,9 +47,6 @@ 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,
@@ -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
-2
View File
@@ -19,8 +19,6 @@ const struct scrcpy_options scrcpy_options_default = {
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, .first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
.last = DEFAULT_LOCAL_PORT_RANGE_LAST, .last = DEFAULT_LOCAL_PORT_RANGE_LAST,
}, },
.tunnel_host = 0,
.tunnel_port = 0,
.shortcut_mods = { .shortcut_mods = {
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, .data = {SC_MOD_LALT, SC_MOD_LSUPER},
.count = 2, .count = 2,
-2
View File
@@ -77,8 +77,6 @@ struct scrcpy_options {
enum sc_record_format record_format; enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode; enum sc_keyboard_input_mode keyboard_input_mode;
struct sc_port_range port_range; struct sc_port_range port_range;
uint32_t tunnel_host;
uint16_t tunnel_port;
struct sc_shortcut_mods shortcut_mods; struct sc_shortcut_mods shortcut_mods;
uint16_t max_size; uint16_t max_size;
uint32_t bit_rate; uint32_t bit_rate;
+2 -2
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;
} }
+31 -79
View File
@@ -34,7 +34,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;
@@ -217,29 +217,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) {
@@ -285,32 +262,6 @@ stream_on_eos(struct stream *stream, void *userdata) {
PUSH_EVENT(EVENT_STREAM_STOPPED); PUSH_EVENT(EVENT_STREAM_STOPPED);
} }
static void
sc_server_on_connection_failed(struct sc_server *server, void *userdata) {
(void) server;
(void) userdata;
PUSH_EVENT(EVENT_SERVER_CONNECTION_FAILED);
}
static void
sc_server_on_connected(struct sc_server *server, void *userdata) {
(void) server;
(void) userdata;
PUSH_EVENT(EVENT_SERVER_CONNECTED);
}
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
scrcpy(struct scrcpy_options *options) { scrcpy(struct scrcpy_options *options) {
static struct scrcpy scrcpy; static struct scrcpy scrcpy;
@@ -324,6 +275,10 @@ scrcpy(struct scrcpy_options *options) {
atexit(SDL_Quit); atexit(SDL_Quit);
if (!server_init(&s->server)) {
return false;
}
bool ret = false; bool ret = false;
bool server_started = false; bool server_started = false;
@@ -340,13 +295,11 @@ 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 = { 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,
.port_range = options->port_range, .port_range = options->port_range,
.tunnel_host = options->tunnel_host,
.tunnel_port = options->tunnel_port,
.max_size = options->max_size, .max_size = options->max_size,
.bit_rate = options->bit_rate, .bit_rate = options->bit_rate,
.max_fps = options->max_fps, .max_fps = options->max_fps,
@@ -360,17 +313,7 @@ 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;
} }
@@ -388,19 +331,14 @@ scrcpy(struct scrcpy_options *options) {
sdl_configure(options->display, options->disable_screensaver); sdl_configure(options->display, options->disable_screensaver);
// Await for server without blocking Ctrl+C handling struct server_info info;
if (!await_for_server()) {
if (!server_connect_to(&s->server, &info)) {
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;
} }
@@ -422,7 +360,7 @@ scrcpy(struct scrcpy_options *options) {
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)) { info.frame_size)) {
goto end; goto end;
} }
rec = &s->recorder; rec = &s->recorder;
@@ -468,11 +406,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 : info.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 = info.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,
@@ -496,7 +434,7 @@ 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,
info->frame_size, options->v4l2_buffer)) { info.frame_size, options->v4l2_buffer)) {
goto end; goto end;
} }
@@ -521,7 +459,21 @@ scrcpy(struct scrcpy_options *options) {
#ifdef HAVE_AOA_HID #ifdef HAVE_AOA_HID
bool aoa_hid_ok = false; bool aoa_hid_ok = false;
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 = sc_aoa_init(&s->aoa, serial); bool ok = sc_aoa_init(&s->aoa, serial);
free(serialno);
if (!ok) { if (!ok) {
goto aoa_hid_end; goto aoa_hid_end;
} }
@@ -599,7 +551,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
@@ -644,7 +596,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;
} }
+34 -39
View File
@@ -224,45 +224,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
@@ -681,6 +642,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;
+7
View File
@@ -93,6 +93,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);
+352 -362
View File
@@ -10,14 +10,14 @@
#include "adb.h" #include "adb.h"
#include "util/file.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,18 +42,18 @@ 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;
} }
#else #else
char *server_path = sc_file_get_local_path(SC_SERVER_FILENAME); char *server_path = sc_file_get_local_path(SERVER_FILENAME);
if (!server_path) { if (!server_path) {
LOGE("Could not get local file path, " LOGE("Could not get local file path, "
"using " SC_SERVER_FILENAME " from current directory"); "using " SERVER_FILENAME " from current directory");
return strdup(SC_SERVER_FILENAME); return strdup(SERVER_FILENAME);
} }
LOGD("Using server (portable): %s", server_path); LOGD("Using server (portable): %s", server_path);
@@ -62,47 +62,8 @@ get_server_path(void) {
return server_path; return server_path;
} }
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;
@@ -112,9 +73,140 @@ push_server(struct sc_intr *intr, const char *serial) {
free(server_path); free(server_path);
return false; return false;
} }
bool ok = adb_push(intr, serial, server_path, SC_DEVICE_SERVER_PATH); sc_pid pid = adb_push(serial, server_path, DEVICE_SERVER_PATH);
free(server_path); free(server_path);
return ok; return sc_process_check_success(pid, "adb push", true);
}
static bool
enable_tunnel_reverse(const char *serial, uint16_t local_port) {
sc_pid pid = adb_reverse(serial, SOCKET_NAME, local_port);
return sc_process_check_success(pid, "adb reverse", true);
}
static bool
disable_tunnel_reverse(const char *serial) {
sc_pid pid = adb_reverse_remove(serial, SOCKET_NAME);
return sc_process_check_success(pid, "adb reverse --remove", true);
}
static bool
enable_tunnel_forward(const char *serial, uint16_t local_port) {
sc_pid pid = adb_forward(serial, local_port, SOCKET_NAME);
return sc_process_check_success(pid, "adb forward", true);
}
static bool
disable_tunnel_forward(const char *serial, uint16_t local_port) {
sc_pid pid = adb_forward_remove(serial, local_port);
return sc_process_check_success(pid, "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 sc_socket
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 != SC_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,10 +229,7 @@ log_level_to_server_string(enum sc_log_level level) {
} }
static sc_pid static sc_pid
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];
@@ -154,7 +243,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"
@@ -176,7 +265,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",
@@ -198,376 +287,277 @@ execute_server(struct sc_server *server,
// Port: 5005 // Port: 5005
// Then click on "Debug" // Then click on "Debug"
#endif #endif
return adb_execute(serial, cmd, ARRAY_LEN(cmd)); return adb_execute(server->serial, cmd, ARRAY_LEN(cmd));
} }
static bool static sc_socket
connect_and_read_byte(struct sc_intr *intr, sc_socket socket, connect_and_read_byte(uint16_t port) {
uint32_t tunnel_host, uint16_t tunnel_port) { sc_socket socket = net_connect(IPV4_LOCALHOST, port);
bool ok = net_connect_intr(intr, socket, tunnel_host, tunnel_port); if (socket == SC_INVALID_SOCKET) {
if (!ok) { return SC_INVALID_SOCKET;
return false;
} }
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 SC_INVALID_SOCKET;
} }
return socket;
return true;
} }
static sc_socket static sc_socket
connect_to_server(struct sc_server *server, uint32_t attempts, sc_tick delay, connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
uint32_t host, uint16_t port) {
do { do {
LOGD("Remaining connection attempts: %d", (int) attempts); LOGD("Remaining connection attempts: %d", (int) attempts);
sc_socket socket = net_socket(); sc_socket socket = connect_and_read_byte(port);
if (socket != SC_SOCKET_NONE) { if (socket != SC_INVALID_SOCKET) {
bool ok = connect_and_read_byte(&server->intr, socket, host, port); // it worked!
if (ok) { return socket;
// it worked!
return socket;
}
net_close(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 SC_INVALID_SOCKET;
} }
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) { server->serial = NULL;
bool ok = sc_server_params_copy(&server->params, params); server->process = SC_PROCESS_NONE;
bool ok = sc_mutex_init(&server->mutex);
if (!ok) { if (!ok) {
LOGE("Could not copy server params");
return false; return false;
} }
ok = sc_mutex_init(&server->mutex); ok = sc_cond_init(&server->process_terminated_cond);
if (!ok) { 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_mutex_destroy(&server->mutex);
sc_server_params_destroy(&server->params);
return false; return false;
} }
ok = sc_intr_init(&server->intr); server->process_terminated = false;
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->server_socket = SC_INVALID_SOCKET;
server->video_socket = SC_INVALID_SOCKET;
server->control_socket = SC_INVALID_SOCKET;
server->video_socket = SC_SOCKET_NONE; server->local_port = 0;
server->control_socket = SC_SOCKET_NONE;
sc_adb_tunnel_init(&server->tunnel); server->tunnel_enabled = false;
server->tunnel_forward = false;
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 tunnel_host = server->params.tunnel_host;
if (!tunnel_host) {
tunnel_host = IPV4_LOCALHOST;
}
uint16_t tunnel_port = server->params.tunnel_port;
if (!tunnel_port) {
tunnel_port = tunnel->local_port;
}
uint32_t attempts = 100;
sc_tick delay = SC_TICK_FROM_MS(100);
video_socket = connect_to_server(server, attempts, delay, tunnel_host,
tunnel_port);
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, tunnel_host,
tunnel_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
sc_server_on_terminated(void *userdata) {
struct sc_server *server = userdata;
// If the server process dies before connecting to the server socket,
// then the client will be stuck forever on accept(). To avoid the problem,
// 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
sc_server_fill_serial(struct sc_server *server) {
// Retrieve the actual device immediately if not provided, so that all
// future adb commands are executed for this specific device, even if other
// devices are connected afterwards (without "more than one
// device/emulator" error)
if (!server->params.serial) {
// The serial is owned by sc_server_params, and will be freed on destroy
server->params.serial = adb_get_serialno(&server->intr);
if (!server->params.serial) {
LOGE("Could not get device serial");
return 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;
sc_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 != SC_INVALID_SOCKET) {
// Unblock any accept()
net_interrupt(server->server_socket);
} }
const struct sc_server_params *params = &server->params; LOGD("Server terminated");
return 0;
}
LOGD("Device serial: %s", params->serial); bool
server_start(struct server *server, const struct server_params *params) {
bool ok = push_server(&server->intr, params->serial); if (params->serial) {
if (!ok) { server->serial = strdup(params->serial);
goto error_connection_failed; if (!server->serial) {
return false;
}
} }
ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, params->serial, if (!push_server(params->serial)) {
params->port_range, params->force_adb_forward); /* server->serial will be freed on server_destroy() */
if (!ok) { return false;
goto error_connection_failed; }
if (!enable_tunnel_any_port(server, params->port_range,
params->force_adb_forward)) {
return false;
} }
// 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 == SC_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); sc_process_terminate(server->process);
sc_process_wait(pid, true); // ignore exit code sc_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); // The server socket (if any) will be closed on server_destroy()
sc_process_observer_destroy(&observer);
goto error_connection_failed; disable_tunnel(server);
return false;
}
static bool
device_read_info(sc_socket device_socket, struct server_info *info) {
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
ssize_t r = net_recv_all(device_socket, buf, sizeof(buf));
if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
LOGE("Could not retrieve device information");
return false;
}
// in case the client sends garbage
buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
memcpy(info->device_name, (char *) buf, sizeof(info->device_name));
info->frame_size.width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8)
| buf[DEVICE_NAME_FIELD_LENGTH + 1];
info->frame_size.height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8)
| buf[DEVICE_NAME_FIELD_LENGTH + 3];
return true;
}
bool
server_connect_to(struct server *server, struct server_info *info) {
if (!server->tunnel_forward) {
server->video_socket = net_accept(server->server_socket);
if (server->video_socket == SC_INVALID_SOCKET) {
return false;
}
server->control_socket = net_accept(server->server_socket);
if (server->control_socket == SC_INVALID_SOCKET) {
// the video_socket will be cleaned up on destroy
return false;
}
// we don't need the server socket anymore
if (!net_close(server->server_socket)) {
LOGW("Could not close server socket on connect");
}
// Do not attempt to close it again on server_destroy()
server->server_socket = SC_INVALID_SOCKET;
} else {
uint32_t attempts = 100;
uint32_t delay = 100; // ms
server->video_socket =
connect_to_server(server->local_port, attempts, delay);
if (server->video_socket == SC_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 == SC_INVALID_SOCKET) {
return false;
}
} }
// Now connected // we don't need the adb tunnel anymore
server->cbs->on_connected(server, server->cbs_userdata); disable_tunnel(server); // ignore failure
server->tunnel_enabled = false;
// Wait for server_stop() // The sockets will be closed on stop if device_read_info() fails
sc_mutex_lock(&server->mutex); return device_read_info(server->video_socket, info);
while (!server->stopped) { }
sc_cond_wait(&server->cond_stopped, &server->mutex);
void
server_stop(struct server *server) {
if (server->server_socket != SC_INVALID_SOCKET) {
if (!net_interrupt(server->server_socket)) {
LOGW("Could not interrupt server socket");
}
}
if (server->video_socket != SC_INVALID_SOCKET) {
if (!net_interrupt(server->video_socket)) {
LOGW("Could not interrupt video socket");
}
}
if (server->control_socket != SC_INVALID_SOCKET) {
if (!net_interrupt(server->control_socket)) {
LOGW("Could not interrupt control socket");
}
}
assert(server->process != SC_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); sc_process_terminate(server->process);
} }
sc_process_observer_join(&observer); sc_thread_join(&server->wait_server_thread, NULL);
sc_process_observer_destroy(&observer); sc_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); if (server->server_socket != SC_INVALID_SOCKET) {
server->stopped = true; if (!net_close(server->server_socket)) {
sc_cond_signal(&server->cond_stopped); LOGW("Could not close server socket");
sc_intr_interrupt(&server->intr); }
sc_mutex_unlock(&server->mutex); }
if (server->video_socket != SC_INVALID_SOCKET) {
sc_thread_join(&server->thread, NULL); if (!net_close(server->video_socket)) {
} LOGW("Could not close video socket");
}
void }
sc_server_destroy(struct sc_server *server) { if (server->control_socket != SC_INVALID_SOCKET) {
sc_server_params_destroy(&server->params); if (!net_close(server->control_socket)) {
sc_intr_destroy(&server->intr); LOGW("Could not close control socket");
sc_cond_destroy(&server->cond_stopped); }
}
free(server->serial);
sc_cond_destroy(&server->process_terminated_cond);
sc_mutex_destroy(&server->mutex); sc_mutex_destroy(&server->mutex);
} }
+31 -56
View File
@@ -8,29 +8,42 @@
#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 "options.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 #define DEVICE_NAME_FIELD_LENGTH 64
struct sc_server_info { struct server_info {
char device_name[SC_DEVICE_NAME_FIELD_LENGTH]; char device_name[DEVICE_NAME_FIELD_LENGTH];
struct sc_size frame_size; struct sc_size frame_size;
}; };
struct sc_server_params { struct server {
char *serial;
sc_pid process;
sc_thread wait_server_thread;
sc_mutex mutex;
sc_cond process_terminated_cond;
bool process_terminated;
sc_socket server_socket; // only used if !tunnel_forward
sc_socket video_socket;
sc_socket control_socket;
uint16_t local_port; // selected from port_range
bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
};
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;
const char *codec_options; const char *codec_options;
const char *encoder_name; const char *encoder_name;
struct sc_port_range port_range; struct sc_port_range port_range;
uint32_t tunnel_host;
uint16_t tunnel_port;
uint16_t max_size; uint16_t max_size;
uint32_t bit_rate; uint32_t bit_rate;
uint16_t max_fps; uint16_t max_fps;
@@ -43,62 +56,24 @@ 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);
// block until the communication with the server is established
bool
server_connect_to(struct server *server, struct server_info *info);
// 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
+3 -3
View File
@@ -5,7 +5,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include "util/log.h" #include "util/log.h"
#include "util/str.h" #include "util/str_util.h"
char * char *
sc_file_get_executable_path(void) { sc_file_get_executable_path(void) {
@@ -19,12 +19,12 @@ sc_file_get_executable_path(void) {
return NULL; return NULL;
} }
buf[len] = '\0'; buf[len] = '\0';
return sc_str_from_wchars(buf); return utf8_from_wide_char(buf);
} }
bool bool
sc_file_is_regular(const char *path) { sc_file_is_regular(const char *path) {
wchar_t *wide_path = sc_str_to_wchars(path); wchar_t *wide_path = utf8_to_wide_char(path);
if (!wide_path) { if (!wide_path) {
LOGC("Could not allocate wide char string"); LOGC("Could not allocate wide char string");
return false; return false;
+22 -83
View File
@@ -1,14 +1,9 @@
// <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 "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,7 +13,7 @@ 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 (%" SC_PRIsizet " chars)", len - 1);
return false; return false;
@@ -31,9 +26,6 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle,
HANDLE *pin, HANDLE *pout, HANDLE *perr) { HANDLE *pin, HANDLE *pout, HANDLE *perr) {
enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC; enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC;
// Add 1 per non-NULL pointer
unsigned handle_count = !!pin + !!pout + !!perr;
SECURITY_ATTRIBUTES sa; SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL; sa.lpSecurityDescriptor = NULL;
@@ -73,94 +65,45 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle,
} }
} }
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 (pin || pout || perr) {
si.dwFlags = STARTF_USESTDHANDLES;
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL;
if (handle_count) {
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
if (pin) { if (pin) {
si.StartupInfo.hStdInput = stdin_read_handle; si.hStdInput = stdin_read_handle;
} }
if (pout) { if (pout) {
si.StartupInfo.hStdOutput = stdout_write_handle; si.hStdOutput = stdout_write_handle;
} }
if (perr) { if (perr) {
si.StartupInfo.hStdError = stderr_write_handle; si.hStdError = stderr_write_handle;
} }
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;
}
// Explicitly pass the HANDLEs that must be inherited
unsigned i = 0;
if (pin) {
handles[i++] = stdin_read_handle;
}
if (pout) {
handles[i++] = stdout_write_handle;
}
if (perr) {
handles[i++] = stderr_write_handle;
}
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, free(wide);
dwCreationFlags, NULL, NULL, &si.StartupInfo, &pi); *handle = NULL;
free(wide);
if (!ok) {
if (GetLastError() == ERROR_FILE_NOT_FOUND) { if (GetLastError() == ERROR_FILE_NOT_FOUND) {
ret = SC_PROCESS_ERROR_MISSING_BINARY; ret = SC_PROCESS_ERROR_MISSING_BINARY;
} }
goto error_free_attribute_list; goto error_close_stderr;
}
if (lpAttributeList) {
DeleteProcThreadAttributeList(lpAttributeList);
free(lpAttributeList);
} }
// 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
@@ -174,15 +117,11 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle,
CloseHandle(stderr_write_handle); CloseHandle(stderr_write_handle);
} }
free(wide);
*handle = pi.hProcess; *handle = pi.hProcess;
return SC_PROCESS_SUCCESS; return SC_PROCESS_SUCCESS;
error_free_attribute_list:
if (lpAttributeList) {
DeleteProcThreadAttributeList(lpAttributeList);
free(lpAttributeList);
}
error_close_stderr: error_close_stderr:
if (perr) { if (perr) {
CloseHandle(*perr); CloseHandle(*perr);
@@ -229,7 +168,7 @@ sc_process_close(HANDLE handle) {
} }
ssize_t ssize_t
sc_pipe_read(HANDLE pipe, char *data, size_t len) { sc_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;
@@ -238,7 +177,7 @@ sc_pipe_read(HANDLE pipe, char *data, size_t len) {
} }
void void
sc_pipe_close(HANDLE pipe) { sc_close_pipe(HANDLE pipe) {
if (!CloseHandle(pipe)) { if (!CloseHandle(pipe)) {
LOGW("Cannot close pipe"); LOGW("Cannot close pipe");
} }
-83
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);
}
-78
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
+23 -37
View File
@@ -1,7 +1,3 @@
// For inet_pton() on Windows
#define _WIN32_WINNT 0x0600
#define WINVER 0x0600
#include "net.h" #include "net.h"
#include <assert.h> #include <assert.h>
@@ -11,7 +7,6 @@
#include "log.h" #include "log.h"
#ifdef __WINDOWS__ #ifdef __WINDOWS__
# include <ws2tcpip.h>
typedef int socklen_t; typedef int socklen_t;
typedef SOCKET sc_raw_socket; typedef SOCKET sc_raw_socket;
#else #else
@@ -51,13 +46,13 @@ static inline sc_socket
wrap(sc_raw_socket sock) { wrap(sc_raw_socket sock) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
if (sock == INVALID_SOCKET) { if (sock == INVALID_SOCKET) {
return SC_SOCKET_NONE; return SC_INVALID_SOCKET;
} }
struct sc_socket_windows *socket = malloc(sizeof(*socket)); struct sc_socket_windows *socket = malloc(sizeof(*socket));
if (!socket) { if (!socket) {
closesocket(sock); closesocket(sock);
return SC_SOCKET_NONE; return SC_INVALID_SOCKET;
} }
socket->socket = sock; socket->socket = sock;
@@ -72,7 +67,7 @@ wrap(sc_raw_socket sock) {
static inline sc_raw_socket static inline sc_raw_socket
unwrap(sc_socket socket) { unwrap(sc_socket socket) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
if (socket == SC_SOCKET_NONE) { if (socket == SC_INVALID_SOCKET) {
return INVALID_SOCKET; return INVALID_SOCKET;
} }
@@ -99,18 +94,13 @@ net_perror(const char *s) {
} }
sc_socket sc_socket
net_socket(void) { net_connect(uint32_t addr, uint16_t port) {
sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM, 0); sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM, 0);
sc_socket sock = wrap(raw_sock); sc_socket sock = wrap(raw_sock);
if (sock == SC_SOCKET_NONE) { if (sock == SC_INVALID_SOCKET) {
net_perror("socket"); net_perror("socket");
return SC_INVALID_SOCKET;
} }
return sock;
}
bool
net_connect(sc_socket socket, uint32_t addr, uint16_t port) {
sc_raw_socket raw_sock = unwrap(socket);
SOCKADDR_IN sin; SOCKADDR_IN sin;
sin.sin_family = AF_INET; sin.sin_family = AF_INET;
@@ -119,15 +109,21 @@ net_connect(sc_socket socket, uint32_t addr, uint16_t port) {
if (connect(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) { if (connect(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
net_perror("connect"); net_perror("connect");
return false; net_close(sock);
return SC_INVALID_SOCKET;
} }
return true; return sock;
} }
bool sc_socket
net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog) { net_listen(uint32_t addr, uint16_t port, int backlog) {
sc_raw_socket raw_sock = unwrap(socket); sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM, 0);
sc_socket sock = wrap(raw_sock);
if (sock == SC_INVALID_SOCKET) {
net_perror("socket");
return SC_INVALID_SOCKET;
}
int reuse = 1; int reuse = 1;
if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse, if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
@@ -142,15 +138,17 @@ net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog) {
if (bind(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) { if (bind(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
net_perror("bind"); net_perror("bind");
return false; net_close(sock);
return SC_INVALID_SOCKET;
} }
if (listen(raw_sock, backlog) == SOCKET_ERROR) { if (listen(raw_sock, backlog) == SOCKET_ERROR) {
net_perror("listen"); net_perror("listen");
return false; net_close(sock);
return SC_INVALID_SOCKET;
} }
return true; return sock;
} }
sc_socket sc_socket
@@ -200,7 +198,7 @@ net_send_all(sc_socket socket, const void *buf, size_t len) {
bool bool
net_interrupt(sc_socket socket) { net_interrupt(sc_socket socket) {
assert(socket != SC_SOCKET_NONE); assert(socket != SC_INVALID_SOCKET);
sc_raw_socket raw_sock = unwrap(socket); sc_raw_socket raw_sock = unwrap(socket);
@@ -230,15 +228,3 @@ net_close(sc_socket socket) {
return !close(raw_sock); return !close(raw_sock);
#endif #endif
} }
bool
net_parse_ipv4(const char *s, uint32_t *ipv4) {
struct in_addr addr;
if (!inet_pton(AF_INET, s, &addr)) {
LOGE("Invalid IPv4 address: %s", s);
return false;
}
*ipv4 = ntohl(addr.s_addr);
return true;
}
+5 -17
View File
@@ -11,7 +11,7 @@
# include <winsock2.h> # include <winsock2.h>
# include <stdatomic.h> # include <stdatomic.h>
# define SC_SOCKET_NONE NULL # define SC_INVALID_SOCKET NULL
typedef struct sc_socket_windows { typedef struct sc_socket_windows {
SOCKET socket; SOCKET socket;
atomic_flag closed; atomic_flag closed;
@@ -20,13 +20,10 @@
#else // not __WINDOWS__ #else // not __WINDOWS__
# include <sys/socket.h> # include <sys/socket.h>
# define SC_SOCKET_NONE -1 # define SC_INVALID_SOCKET -1
typedef int sc_socket; typedef int sc_socket;
#endif #endif
#define IPV4_LOCALHOST 0x7F000001
bool bool
net_init(void); net_init(void);
@@ -34,13 +31,10 @@ void
net_cleanup(void); net_cleanup(void);
sc_socket sc_socket
net_socket(void); net_connect(uint32_t addr, uint16_t port);
bool sc_socket
net_connect(sc_socket socket, uint32_t addr, uint16_t port); net_listen(uint32_t addr, uint16_t port, int backlog);
bool
net_listen(sc_socket socket, uint32_t addr, uint16_t port, int backlog);
sc_socket sc_socket
net_accept(sc_socket server_socket); net_accept(sc_socket server_socket);
@@ -68,10 +62,4 @@ net_interrupt(sc_socket socket);
bool bool
net_close(sc_socket socket); net_close(sc_socket socket);
/**
* Parse `ip` "xxx.xxx.xxx.xxx" to an IPv4 host representation
*/
bool
net_parse_ipv4(const char *ip, uint32_t *ipv4);
#endif #endif
-97
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;
}
-35
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
-78
View File
@@ -1,6 +1,5 @@
#include "process.h" #include "process.h"
#include <assert.h>
#include <libgen.h> #include <libgen.h>
#include "log.h" #include "log.h"
@@ -42,80 +41,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);
}
-67
View File
@@ -4,7 +4,6 @@
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include "util/thread.h"
#ifdef _WIN32 #ifdef _WIN32
@@ -33,34 +32,6 @@
#endif #endif
struct sc_process_listener {
void (*on_terminated)(void *userdata);
};
/**
* Tool to observe process termination
*
* 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;
sc_cond cond_terminated;
bool terminated;
sc_thread thread;
const struct sc_process_listener *listener;
void *listener_userdata;
};
enum sc_process_result { enum sc_process_result {
SC_PROCESS_SUCCESS, SC_PROCESS_SUCCESS,
SC_PROCESS_ERROR_GENERIC, SC_PROCESS_ERROR_GENERIC,
@@ -135,42 +106,4 @@ sc_pipe_read_all(sc_pipe pipe, char *data, size_t len);
void void
sc_pipe_close(sc_pipe pipe); sc_pipe_close(sc_pipe 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
-50
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;
}
-21
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
-117
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
+13 -23
View File
@@ -1,4 +1,4 @@
#include "str.h" #include "str_util.h"
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
@@ -13,7 +13,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 +23,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 +33,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 +47,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 +61,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 +80,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 +109,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 +143,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 +161,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 +179,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 +195,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;
@@ -213,8 +212,7 @@ sc_str_from_wchars(const wchar_t *ws) {
#endif #endif
char * char *sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent) {
sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent) {
assert(indent < columns); assert(indent < columns);
struct sc_strbuf buf; struct sc_strbuf buf;
@@ -291,11 +289,3 @@ error:
free(buf.s); free(buf.s);
return NULL; 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;
}
+73
View File
@@ -0,0 +1,73 @@
#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
/**
* 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);
#endif
+2 -2
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;
} }
@@ -5,11 +5,11 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include "util/str.h" #include "util/str_util.h"
static void test_strncpy_simple(void) { static void test_xstrncpy_simple(void) {
char s[] = "xxxxxxxxxx"; char s[] = "xxxxxxxxxx";
size_t w = sc_strncpy(s, "abcdef", sizeof(s)); size_t w = xstrncpy(s, "abcdef", sizeof(s));
// returns strlen of copied string // returns strlen of copied string
assert(w == 6); assert(w == 6);
@@ -24,9 +24,9 @@ static void test_strncpy_simple(void) {
assert(!strcmp("abcdef", s)); assert(!strcmp("abcdef", s));
} }
static void test_strncpy_just_fit(void) { static void test_xstrncpy_just_fit(void) {
char s[] = "xxxxxx"; char s[] = "xxxxxx";
size_t w = sc_strncpy(s, "abcdef", sizeof(s)); size_t w = xstrncpy(s, "abcdef", sizeof(s));
// returns strlen of copied string // returns strlen of copied string
assert(w == 6); assert(w == 6);
@@ -38,9 +38,9 @@ static void test_strncpy_just_fit(void) {
assert(!strcmp("abcdef", s)); assert(!strcmp("abcdef", s));
} }
static void test_strncpy_truncated(void) { static void test_xstrncpy_truncated(void) {
char s[] = "xxx"; char s[] = "xxx";
size_t w = sc_strncpy(s, "abcdef", sizeof(s)); size_t w = xstrncpy(s, "abcdef", sizeof(s));
// returns 'n' (sizeof(s)) // returns 'n' (sizeof(s))
assert(w == 4); assert(w == 4);
@@ -52,10 +52,10 @@ static void test_strncpy_truncated(void) {
assert(!strncmp("abcdef", s, 3)); assert(!strncmp("abcdef", s, 3));
} }
static void test_join_simple(void) { static void test_xstrjoin_simple(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL }; const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxxxxxxxxx"; char s[] = "xxxxxxxxxxxxxx";
size_t w = sc_str_join(s, tokens, ' ', sizeof(s)); size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
// returns strlen of concatenation // returns strlen of concatenation
assert(w == 11); assert(w == 11);
@@ -70,10 +70,10 @@ static void test_join_simple(void) {
assert(!strcmp("abc de fghi", s)); assert(!strcmp("abc de fghi", s));
} }
static void test_join_just_fit(void) { static void test_xstrjoin_just_fit(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL }; const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxxxxxx"; char s[] = "xxxxxxxxxxx";
size_t w = sc_str_join(s, tokens, ' ', sizeof(s)); size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
// returns strlen of concatenation // returns strlen of concatenation
assert(w == 11); assert(w == 11);
@@ -85,10 +85,10 @@ static void test_join_just_fit(void) {
assert(!strcmp("abc de fghi", s)); assert(!strcmp("abc de fghi", s));
} }
static void test_join_truncated_in_token(void) { static void test_xstrjoin_truncated_in_token(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL }; const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxx"; char s[] = "xxxxx";
size_t w = sc_str_join(s, tokens, ' ', sizeof(s)); size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
// returns 'n' (sizeof(s)) // returns 'n' (sizeof(s))
assert(w == 6); assert(w == 6);
@@ -100,10 +100,10 @@ static void test_join_truncated_in_token(void) {
assert(!strcmp("abc d", s)); assert(!strcmp("abc d", s));
} }
static void test_join_truncated_before_sep(void) { static void test_xstrjoin_truncated_before_sep(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL }; const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxx"; char s[] = "xxxxxx";
size_t w = sc_str_join(s, tokens, ' ', sizeof(s)); size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
// returns 'n' (sizeof(s)) // returns 'n' (sizeof(s))
assert(w == 7); assert(w == 7);
@@ -115,10 +115,10 @@ static void test_join_truncated_before_sep(void) {
assert(!strcmp("abc de", s)); assert(!strcmp("abc de", s));
} }
static void test_join_truncated_after_sep(void) { static void test_xstrjoin_truncated_after_sep(void) {
const char *const tokens[] = { "abc", "de", "fghi", NULL }; const char *const tokens[] = { "abc", "de", "fghi", NULL };
char s[] = "xxxxxxx"; char s[] = "xxxxxxx";
size_t w = sc_str_join(s, tokens, ' ', sizeof(s)); size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
// returns 'n' (sizeof(s)) // returns 'n' (sizeof(s))
assert(w == 8); assert(w == 8);
@@ -130,9 +130,9 @@ static void test_join_truncated_after_sep(void) {
assert(!strcmp("abc de ", s)); assert(!strcmp("abc de ", s));
} }
static void test_quote(void) { static void test_strquote(void) {
const char *s = "abcde"; const char *s = "abcde";
char *out = sc_str_quote(s); char *out = strquote(s);
// add '"' at the beginning and the end // add '"' at the beginning and the end
assert(!strcmp("\"abcde\"", out)); assert(!strcmp("\"abcde\"", out));
@@ -146,71 +146,71 @@ static void test_utf8_truncate(void) {
size_t count; size_t count;
count = sc_str_utf8_truncation_index(s, 1); count = utf8_truncation_index(s, 1);
assert(count == 1); assert(count == 1);
count = sc_str_utf8_truncation_index(s, 2); count = utf8_truncation_index(s, 2);
assert(count == 1); // É is 2 bytes-wide assert(count == 1); // É is 2 bytes-wide
count = sc_str_utf8_truncation_index(s, 3); count = utf8_truncation_index(s, 3);
assert(count == 3); assert(count == 3);
count = sc_str_utf8_truncation_index(s, 4); count = utf8_truncation_index(s, 4);
assert(count == 4); assert(count == 4);
count = sc_str_utf8_truncation_index(s, 5); count = utf8_truncation_index(s, 5);
assert(count == 4); // Ô is 2 bytes-wide assert(count == 4); // Ô is 2 bytes-wide
count = sc_str_utf8_truncation_index(s, 6); count = utf8_truncation_index(s, 6);
assert(count == 6); assert(count == 6);
count = sc_str_utf8_truncation_index(s, 7); count = utf8_truncation_index(s, 7);
assert(count == 7); assert(count == 7);
count = sc_str_utf8_truncation_index(s, 8); count = utf8_truncation_index(s, 8);
assert(count == 7); // no more chars assert(count == 7); // no more chars
} }
static void test_parse_integer(void) { static void test_parse_integer(void) {
long value; long value;
bool ok = sc_str_parse_integer("1234", &value); bool ok = parse_integer("1234", &value);
assert(ok); assert(ok);
assert(value == 1234); assert(value == 1234);
ok = sc_str_parse_integer("-1234", &value); ok = parse_integer("-1234", &value);
assert(ok); assert(ok);
assert(value == -1234); assert(value == -1234);
ok = sc_str_parse_integer("1234k", &value); ok = parse_integer("1234k", &value);
assert(!ok); assert(!ok);
ok = sc_str_parse_integer("123456789876543212345678987654321", &value); ok = parse_integer("123456789876543212345678987654321", &value);
assert(!ok); // out-of-range assert(!ok); // out-of-range
} }
static void test_parse_integers(void) { static void test_parse_integers(void) {
long values[5]; long values[5];
size_t count = sc_str_parse_integers("1234", ':', 5, values); size_t count = parse_integers("1234", ':', 5, values);
assert(count == 1); assert(count == 1);
assert(values[0] == 1234); assert(values[0] == 1234);
count = sc_str_parse_integers("1234:5678", ':', 5, values); count = parse_integers("1234:5678", ':', 5, values);
assert(count == 2); assert(count == 2);
assert(values[0] == 1234); assert(values[0] == 1234);
assert(values[1] == 5678); assert(values[1] == 5678);
count = sc_str_parse_integers("1234:5678", ':', 2, values); count = parse_integers("1234:5678", ':', 2, values);
assert(count == 2); assert(count == 2);
assert(values[0] == 1234); assert(values[0] == 1234);
assert(values[1] == 5678); assert(values[1] == 5678);
count = sc_str_parse_integers("1234:-5678", ':', 2, values); count = parse_integers("1234:-5678", ':', 2, values);
assert(count == 2); assert(count == 2);
assert(values[0] == 1234); assert(values[0] == 1234);
assert(values[1] == -5678); assert(values[1] == -5678);
count = sc_str_parse_integers("1:2:3:4:5", ':', 5, values); count = parse_integers("1:2:3:4:5", ':', 5, values);
assert(count == 5); assert(count == 5);
assert(values[0] == 1); assert(values[0] == 1);
assert(values[1] == 2); assert(values[1] == 2);
@@ -218,85 +218,85 @@ static void test_parse_integers(void) {
assert(values[3] == 4); assert(values[3] == 4);
assert(values[4] == 5); assert(values[4] == 5);
count = sc_str_parse_integers("1234:5678", ':', 1, values); count = parse_integers("1234:5678", ':', 1, values);
assert(count == 0); // max_items == 1 assert(count == 0); // max_items == 1
count = sc_str_parse_integers("1:2:3:4:5", ':', 3, values); count = parse_integers("1:2:3:4:5", ':', 3, values);
assert(count == 0); // max_items == 3 assert(count == 0); // max_items == 3
count = sc_str_parse_integers(":1234", ':', 5, values); count = parse_integers(":1234", ':', 5, values);
assert(count == 0); // invalid assert(count == 0); // invalid
count = sc_str_parse_integers("1234:", ':', 5, values); count = parse_integers("1234:", ':', 5, values);
assert(count == 0); // invalid assert(count == 0); // invalid
count = sc_str_parse_integers("1234:", ':', 1, values); count = parse_integers("1234:", ':', 1, values);
assert(count == 0); // invalid, even when max_items == 1 assert(count == 0); // invalid, even when max_items == 1
count = sc_str_parse_integers("1234::5678", ':', 5, values); count = parse_integers("1234::5678", ':', 5, values);
assert(count == 0); // invalid assert(count == 0); // invalid
} }
static void test_parse_integer_with_suffix(void) { static void test_parse_integer_with_suffix(void) {
long value; long value;
bool ok = sc_str_parse_integer_with_suffix("1234", &value); bool ok = parse_integer_with_suffix("1234", &value);
assert(ok); assert(ok);
assert(value == 1234); assert(value == 1234);
ok = sc_str_parse_integer_with_suffix("-1234", &value); ok = parse_integer_with_suffix("-1234", &value);
assert(ok); assert(ok);
assert(value == -1234); assert(value == -1234);
ok = sc_str_parse_integer_with_suffix("1234k", &value); ok = parse_integer_with_suffix("1234k", &value);
assert(ok); assert(ok);
assert(value == 1234000); assert(value == 1234000);
ok = sc_str_parse_integer_with_suffix("1234m", &value); ok = parse_integer_with_suffix("1234m", &value);
assert(ok); assert(ok);
assert(value == 1234000000); assert(value == 1234000000);
ok = sc_str_parse_integer_with_suffix("-1234k", &value); ok = parse_integer_with_suffix("-1234k", &value);
assert(ok); assert(ok);
assert(value == -1234000); assert(value == -1234000);
ok = sc_str_parse_integer_with_suffix("-1234m", &value); ok = parse_integer_with_suffix("-1234m", &value);
assert(ok); assert(ok);
assert(value == -1234000000); assert(value == -1234000000);
ok = sc_str_parse_integer_with_suffix("123456789876543212345678987654321", &value); ok = parse_integer_with_suffix("123456789876543212345678987654321", &value);
assert(!ok); // out-of-range assert(!ok); // out-of-range
char buf[32]; char buf[32];
sprintf(buf, "%ldk", LONG_MAX / 2000); sprintf(buf, "%ldk", LONG_MAX / 2000);
ok = sc_str_parse_integer_with_suffix(buf, &value); ok = parse_integer_with_suffix(buf, &value);
assert(ok); assert(ok);
assert(value == LONG_MAX / 2000 * 1000); assert(value == LONG_MAX / 2000 * 1000);
sprintf(buf, "%ldm", LONG_MAX / 2000); sprintf(buf, "%ldm", LONG_MAX / 2000);
ok = sc_str_parse_integer_with_suffix(buf, &value); ok = parse_integer_with_suffix(buf, &value);
assert(!ok); assert(!ok);
sprintf(buf, "%ldk", LONG_MIN / 2000); sprintf(buf, "%ldk", LONG_MIN / 2000);
ok = sc_str_parse_integer_with_suffix(buf, &value); ok = parse_integer_with_suffix(buf, &value);
assert(ok); assert(ok);
assert(value == LONG_MIN / 2000 * 1000); assert(value == LONG_MIN / 2000 * 1000);
sprintf(buf, "%ldm", LONG_MIN / 2000); sprintf(buf, "%ldm", LONG_MIN / 2000);
ok = sc_str_parse_integer_with_suffix(buf, &value); ok = parse_integer_with_suffix(buf, &value);
assert(!ok); assert(!ok);
} }
static void test_strlist_contains(void) { static void test_strlist_contains(void) {
assert(sc_str_list_contains("a,bc,def", ',', "bc")); assert(strlist_contains("a,bc,def", ',', "bc"));
assert(!sc_str_list_contains("a,bc,def", ',', "b")); assert(!strlist_contains("a,bc,def", ',', "b"));
assert(sc_str_list_contains("", ',', "")); assert(strlist_contains("", ',', ""));
assert(sc_str_list_contains("abc,", ',', "")); assert(strlist_contains("abc,", ',', ""));
assert(sc_str_list_contains(",abc", ',', "")); assert(strlist_contains(",abc", ',', ""));
assert(sc_str_list_contains("abc,,def", ',', "")); assert(strlist_contains("abc,,def", ',', ""));
assert(!sc_str_list_contains("abc", ',', "")); assert(!strlist_contains("abc", ',', ""));
assert(sc_str_list_contains(",,|x", '|', ",,")); assert(strlist_contains(",,|x", '|', ",,"));
assert(sc_str_list_contains("xyz", '\0', "xyz")); assert(strlist_contains("xyz", '\0', "xyz"));
} }
static void test_wrap_lines(void) { static void test_wrap_lines(void) {
@@ -337,51 +337,24 @@ static void test_wrap_lines(void) {
free(formatted); 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[]) { int main(int argc, char *argv[]) {
(void) argc; (void) argc;
(void) argv; (void) argv;
test_strncpy_simple(); test_xstrncpy_simple();
test_strncpy_just_fit(); test_xstrncpy_just_fit();
test_strncpy_truncated(); test_xstrncpy_truncated();
test_join_simple(); test_xstrjoin_simple();
test_join_just_fit(); test_xstrjoin_just_fit();
test_join_truncated_in_token(); test_xstrjoin_truncated_in_token();
test_join_truncated_before_sep(); test_xstrjoin_truncated_before_sep();
test_join_truncated_after_sep(); test_xstrjoin_truncated_after_sep();
test_quote(); test_strquote();
test_utf8_truncate(); test_utf8_truncate();
test_parse_integer(); test_parse_integer();
test_parse_integers(); test_parse_integers();
test_parse_integer_with_suffix(); test_parse_integer_with_suffix();
test_strlist_contains(); test_strlist_contains();
test_wrap_lines(); test_wrap_lines();
test_truncate();
return 0; return 0;
} }
+2 -2
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
+1 -1
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',
+3 -3
View File
@@ -6,8 +6,8 @@ android {
applicationId "com.genymobile.scrcpy" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 31
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"
+15 -34
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,40 +47,23 @@ 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"
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
--output "$BUILD_DIR/classes.dex" \
android/view/*.class \
android/content/*.class \
com/genymobile/scrcpy/*.class \
com/genymobile/scrcpy/wrappers/*.class
if [[ $PLATFORM_VERSION -lt 31 ]] echo "Archiving..."
then cd "$BUILD_DIR"
# use dx jar cvf "$SERVER_BINARY" classes.dex
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \ rm -rf classes.dex classes
--output "$BUILD_DIR/classes.dex" \
android/view/*.class \
android/content/*.class \
com/genymobile/scrcpy/*.class \
com/genymobile/scrcpy/wrappers/*.class
echo "Archiving..."
cd "$BUILD_DIR"
jar cvf "$SERVER_BINARY" classes.dex
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"
@@ -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\""); settings.putValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
try {
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);
} }
} }
} }
@@ -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;
}
}
@@ -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();
} }
} }
@@ -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) {
@@ -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
@@ -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;
}
}
@@ -1,11 +0,0 @@
package com.genymobile.scrcpy;
public class SettingsException extends Exception {
private static String createMessage(String method, String table, String key, String value) {
return "Could not access settings: " + method + " " + table + " " + key + (value != null ? " " + value : "");
}
public SettingsException(String method, String table, String key, String value, Throwable cause) {
super(createMessage(method, table, key, value), cause);
}
}
@@ -1,7 +1,6 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln; import com.genymobile.scrcpy.Ln;
import com.genymobile.scrcpy.SettingsException;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.os.Bundle; import android.os.Bundle;
@@ -88,8 +87,7 @@ public class ContentProvider implements Closeable {
return attributionSource; return attributionSource;
} }
private Bundle call(String callMethod, String arg, Bundle extras) private Bundle call(String callMethod, String arg, Bundle extras) {
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
try { try {
Method method = getCallMethod(); Method method = getCallMethod();
Object[] args; Object[] args;
@@ -110,7 +108,7 @@ public class ContentProvider implements Closeable {
return (Bundle) method.invoke(provider, args); return (Bundle) method.invoke(provider, args);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException | InstantiationException e) { } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException | InstantiationException e) {
Ln.e("Could not invoke method", e); Ln.e("Could not invoke method", e);
throw e; return null;
} }
} }
@@ -144,31 +142,30 @@ public class ContentProvider implements Closeable {
} }
} }
public String getValue(String table, String key) throws SettingsException { public String getValue(String table, String key) {
String method = getGetMethod(table); String method = getGetMethod(table);
Bundle arg = new Bundle(); Bundle arg = new Bundle();
arg.putInt(CALL_METHOD_USER_KEY, ServiceManager.USER_ID); arg.putInt(CALL_METHOD_USER_KEY, ServiceManager.USER_ID);
try { Bundle bundle = call(method, key, arg);
Bundle bundle = call(method, key, arg); if (bundle == null) {
if (bundle == null) { return null;
return null;
}
return bundle.getString("value");
} catch (Exception e) {
throw new SettingsException(table, "get", key, null, e);
} }
return bundle.getString("value");
} }
public void putValue(String table, String key, String value) throws SettingsException { public void putValue(String table, String key, String value) {
String method = getPutMethod(table); String method = getPutMethod(table);
Bundle arg = new Bundle(); Bundle arg = new Bundle();
arg.putInt(CALL_METHOD_USER_KEY, ServiceManager.USER_ID); arg.putInt(CALL_METHOD_USER_KEY, ServiceManager.USER_ID);
arg.putString(NAME_VALUE_TABLE_VALUE, value); arg.putString(NAME_VALUE_TABLE_VALUE, value);
try { call(method, key, arg);
call(method, key, arg); }
} catch (Exception e) {
throw new SettingsException(table, "put", key, value, e); public String getAndPutValue(String table, String key, String value) {
String oldValue = getValue(table, key);
if (!value.equals(oldValue)) {
putValue(table, key, value);
} }
return oldValue;
} }
} }