Compare commits
2 Commits
adb_serial
...
server_thr
Author | SHA1 | Date | |
---|---|---|---|
781bb7397b | |||
b876cf9ddc |
6
BUILD.md
6
BUILD.md
@ -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
FAQ.it.md
18
FAQ.it.md
@ -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
|
||||||
|
|
||||||
|
23
FAQ.md
23
FAQ.md
@ -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)
|
||||||
|
121
README.it.md
121
README.it.md
@ -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.
|
||||||
|
|
||||||
|
132
README.jp.md
132
README.jp.md
@ -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`で構成します:
|
||||||
|
61
README.md
61
README.md
@ -1,37 +1,26 @@
|
|||||||
# scrcpy (v1.20)
|
# scrcpy (v1.19)
|
||||||
|
|
||||||
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
|

|
||||||
|
|
||||||
[Read in another language](#translations)
|
[Read in another language](#translations)
|
||||||
|
|
||||||
This application provides display and control of Android devices connected via
|
This application provides display and control of Android devices connected on
|
||||||
USB (or [over TCP/IP](#wireless)). It does not require any _root_ access.
|
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
|
||||||
It works on _GNU/Linux_, _Windows_ and _macOS_.
|
It works on _GNU/Linux_, _Windows_ and _macOS_.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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:
|
||||||
|
|
||||||
@ -595,14 +582,6 @@ scrcpy --turn-screen-off --stay-awake
|
|||||||
scrcpy -Sw
|
scrcpy -Sw
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Power off on close
|
|
||||||
|
|
||||||
To turn the device screen off when closing scrcpy:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --power-off-on-close
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### Show touches
|
#### Show touches
|
||||||
|
|
||||||
@ -699,7 +678,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.
|
||||||
|
|
||||||
@ -851,7 +830,7 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
|||||||
| Turn device screen on | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
| Turn device screen on | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
||||||
| Rotate device screen | <kbd>MOD</kbd>+<kbd>r</kbd>
|
| Rotate device screen | <kbd>MOD</kbd>+<kbd>r</kbd>
|
||||||
| Expand notification panel | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5th-click³_
|
| Expand notification panel | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5th-click³_
|
||||||
| Expand settings panel | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Double-5th-click³_
|
| Expand settings panel | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Double-5th-click³_
|
||||||
| Collapse panels | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
| Collapse panels | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||||
| Copy to clipboard⁴ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
| Copy to clipboard⁴ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||||
| Cut to clipboard⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
| Cut to clipboard⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||||
@ -859,8 +838,6 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
|||||||
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
||||||
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||||
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
|
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
|
||||||
| Drag & drop APK file | Install APK from computer
|
|
||||||
| Drag & drop non-APK file | [Push file to device](#push-file-to-device)
|
|
||||||
|
|
||||||
_¹Double-click on black borders to remove them._
|
_¹Double-click on black borders to remove them._
|
||||||
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
||||||
@ -950,10 +927,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)
|
||||||
|
174
README.pt-br.md
174
README.pt-br.md
@ -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
|
||||||
|
|
||||||
|
@ -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',
|
||||||
@ -25,30 +24,18 @@ src = [
|
|||||||
'src/server.c',
|
'src/server.c',
|
||||||
'src/stream.c',
|
'src/stream.c',
|
||||||
'src/video_buffer.c',
|
'src/video_buffer.c',
|
||||||
'src/util/file.c',
|
|
||||||
'src/util/intr.c',
|
|
||||||
'src/util/log.c',
|
'src/util/log.c',
|
||||||
'src/util/net.c',
|
'src/util/net.c',
|
||||||
'src/util/net_intr.c',
|
|
||||||
'src/util/process.c',
|
'src/util/process.c',
|
||||||
'src/util/process_intr.c',
|
'src/util/str_util.c',
|
||||||
'src/util/strbuf.c',
|
|
||||||
'src/util/str.c',
|
|
||||||
'src/util/term.c',
|
|
||||||
'src/util/thread.c',
|
'src/util/thread.c',
|
||||||
'src/util/tick.c',
|
'src/util/tick.c',
|
||||||
]
|
]
|
||||||
|
|
||||||
if host_machine.system() == 'windows'
|
if host_machine.system() == 'windows'
|
||||||
src += [
|
src += [ 'src/sys/win/process.c' ]
|
||||||
'src/sys/win/file.c',
|
|
||||||
'src/sys/win/process.c',
|
|
||||||
]
|
|
||||||
else
|
else
|
||||||
src += [
|
src += [ 'src/sys/unix/process.c' ]
|
||||||
'src/sys/unix/file.c',
|
|
||||||
'src/sys/unix/process.c',
|
|
||||||
]
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
v4l2_support = host_machine.system() == 'linux'
|
v4l2_support = host_machine.system() == 'linux'
|
||||||
@ -199,9 +186,7 @@ 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/str.c',
|
'src/util/str_util.c',
|
||||||
'src/util/strbuf.c',
|
|
||||||
'src/util/term.c',
|
|
||||||
]],
|
]],
|
||||||
['test_clock', [
|
['test_clock', [
|
||||||
'tests/test_clock.c',
|
'tests/test_clock.c',
|
||||||
@ -210,8 +195,7 @@ if get_option('buildtype') == 'debug'
|
|||||||
['test_control_msg_serialize', [
|
['test_control_msg_serialize', [
|
||||||
'tests/test_control_msg_serialize.c',
|
'tests/test_control_msg_serialize.c',
|
||||||
'src/control_msg.c',
|
'src/control_msg.c',
|
||||||
'src/util/str.c',
|
'src/util/str_util.c',
|
||||||
'src/util/strbuf.c',
|
|
||||||
]],
|
]],
|
||||||
['test_device_msg_deserialize', [
|
['test_device_msg_deserialize', [
|
||||||
'tests/test_device_msg_deserialize.c',
|
'tests/test_device_msg_deserialize.c',
|
||||||
@ -220,14 +204,9 @@ if get_option('buildtype') == 'debug'
|
|||||||
['test_queue', [
|
['test_queue', [
|
||||||
'tests/test_queue.c',
|
'tests/test_queue.c',
|
||||||
]],
|
]],
|
||||||
['test_strbuf', [
|
['test_strutil', [
|
||||||
'tests/test_strbuf.c',
|
'tests/test_strutil.c',
|
||||||
'src/util/strbuf.c',
|
'src/util/str_util.c',
|
||||||
]],
|
|
||||||
['test_str', [
|
|
||||||
'tests/test_str.c',
|
|
||||||
'src/util/str.c',
|
|
||||||
'src/util/strbuf.c',
|
|
||||||
]],
|
]],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -136,10 +136,6 @@ Set the TCP port (range) used by the client to listen.
|
|||||||
|
|
||||||
Default is 27183:27199.
|
Default is 27183:27199.
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-\-power\-off\-on\-close
|
|
||||||
Turn the device screen off when closing scrcpy.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-prefer\-text
|
.B \-\-prefer\-text
|
||||||
Inject alpha characters and space as text events instead of key events.
|
Inject alpha characters and space as text events instead of key events.
|
||||||
@ -368,10 +364,6 @@ Pinch-to-zoom from the center of the screen
|
|||||||
.B Drag & drop APK file
|
.B Drag & drop APK file
|
||||||
Install APK from computer
|
Install APK from computer
|
||||||
|
|
||||||
.TP
|
|
||||||
.B Drag & drop non-APK file
|
|
||||||
Push file to device (see \fB\-\-push\-target\fR)
|
|
||||||
|
|
||||||
|
|
||||||
.SH Environment variables
|
.SH Environment variables
|
||||||
|
|
||||||
|
117
app/src/adb.c
117
app/src/adb.c
@ -5,9 +5,8 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "util/file.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
static const char *adb_command;
|
static const char *adb_command;
|
||||||
|
|
||||||
@ -69,7 +68,7 @@ show_adb_installation_msg() {
|
|||||||
{"pacman", "pacman -S android-tools"},
|
{"pacman", "pacman -S android-tools"},
|
||||||
};
|
};
|
||||||
for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) {
|
for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) {
|
||||||
if (sc_file_executable_exists(pkg_managers[i].binary)) {
|
if (search_executable(pkg_managers[i].binary)) {
|
||||||
LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command);
|
LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -81,7 +80,7 @@ show_adb_installation_msg() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
show_adb_err_msg(enum sc_process_result err, const char *const argv[]) {
|
show_adb_err_msg(enum process_result err, const char *const argv[]) {
|
||||||
#define MAX_COMMAND_STRING_LEN 1024
|
#define MAX_COMMAND_STRING_LEN 1024
|
||||||
char *buf = malloc(MAX_COMMAND_STRING_LEN);
|
char *buf = malloc(MAX_COMMAND_STRING_LEN);
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
@ -90,18 +89,18 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (err) {
|
switch (err) {
|
||||||
case SC_PROCESS_ERROR_GENERIC:
|
case PROCESS_ERROR_GENERIC:
|
||||||
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
|
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
|
||||||
LOGE("Failed to execute: %s", buf);
|
LOGE("Failed to execute: %s", buf);
|
||||||
break;
|
break;
|
||||||
case SC_PROCESS_ERROR_MISSING_BINARY:
|
case PROCESS_ERROR_MISSING_BINARY:
|
||||||
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
|
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
|
||||||
LOGE("Command not found: %s", buf);
|
LOGE("Command not found: %s", buf);
|
||||||
LOGE("(make 'adb' accessible from your PATH or define its full"
|
LOGE("(make 'adb' accessible from your PATH or define its full"
|
||||||
"path in the ADB environment variable)");
|
"path in the ADB environment variable)");
|
||||||
show_adb_installation_msg();
|
show_adb_installation_msg();
|
||||||
break;
|
break;
|
||||||
case SC_PROCESS_SUCCESS:
|
case PROCESS_SUCCESS:
|
||||||
// do nothing
|
// do nothing
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -109,15 +108,16 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) {
|
|||||||
free(buf);
|
free(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_pid
|
process_t
|
||||||
adb_execute_p(const char *serial, const char *const adb_cmd[],
|
adb_execute_redirect(const char *serial, const char *const adb_cmd[],
|
||||||
size_t len, sc_pipe *pout) {
|
size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout,
|
||||||
|
pipe_t *pipe_stderr) {
|
||||||
int i;
|
int i;
|
||||||
sc_pid pid;
|
process_t process;
|
||||||
|
|
||||||
const char **argv = malloc((len + 4) * sizeof(*argv));
|
const char **argv = malloc((len + 4) * sizeof(*argv));
|
||||||
if (!argv) {
|
if (!argv) {
|
||||||
return SC_PROCESS_NONE;
|
return PROCESS_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
argv[0] = get_adb_command();
|
argv[0] = get_adb_command();
|
||||||
@ -131,23 +131,24 @@ adb_execute_p(const char *serial, const char *const adb_cmd[],
|
|||||||
|
|
||||||
memcpy(&argv[i], adb_cmd, len * sizeof(const char *));
|
memcpy(&argv[i], adb_cmd, len * sizeof(const char *));
|
||||||
argv[len + i] = NULL;
|
argv[len + i] = NULL;
|
||||||
enum sc_process_result r =
|
enum process_result r =
|
||||||
sc_process_execute_p(argv, &pid, NULL, pout, NULL);
|
process_execute_redirect(argv, &process, pipe_stdin, pipe_stdout,
|
||||||
if (r != SC_PROCESS_SUCCESS) {
|
pipe_stderr);
|
||||||
|
if (r != PROCESS_SUCCESS) {
|
||||||
show_adb_err_msg(r, argv);
|
show_adb_err_msg(r, argv);
|
||||||
pid = SC_PROCESS_NONE;
|
process = PROCESS_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(argv);
|
free(argv);
|
||||||
return pid;
|
return process;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_pid
|
process_t
|
||||||
adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
|
adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
|
||||||
return adb_execute_p(serial, adb_cmd, len, NULL);
|
return adb_execute_redirect(serial, adb_cmd, len, NULL, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_pid
|
process_t
|
||||||
adb_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
|
||||||
@ -158,7 +159,7 @@ adb_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));
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_pid
|
process_t
|
||||||
adb_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);
|
||||||
@ -166,7 +167,7 @@ adb_forward_remove(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));
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_pid
|
process_t
|
||||||
adb_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
|
||||||
@ -177,7 +178,7 @@ adb_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));
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_pid
|
process_t
|
||||||
adb_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);
|
||||||
@ -185,56 +186,94 @@ adb_reverse_remove(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));
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_pid
|
process_t
|
||||||
adb_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 PROCESS_NONE;
|
||||||
}
|
}
|
||||||
remote = sc_str_quote(remote);
|
remote = strquote(remote);
|
||||||
if (!remote) {
|
if (!remote) {
|
||||||
free((void *) local);
|
free((void *) local);
|
||||||
return SC_PROCESS_NONE;
|
return PROCESS_NONE;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const char *const adb_cmd[] = {"push", local, remote};
|
const char *const adb_cmd[] = {"push", local, remote};
|
||||||
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
|
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
|
||||||
|
|
||||||
#ifdef __WINDOWS__
|
#ifdef __WINDOWS__
|
||||||
free((void *) remote);
|
free((void *) remote);
|
||||||
free((void *) local);
|
free((void *) local);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return pid;
|
return proc;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_pid
|
process_t
|
||||||
adb_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 PROCESS_NONE;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const char *const adb_cmd[] = {"install", "-r", local};
|
const char *const adb_cmd[] = {"install", "-r", local};
|
||||||
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
|
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
|
||||||
|
|
||||||
#ifdef __WINDOWS__
|
#ifdef __WINDOWS__
|
||||||
free((void *) local);
|
free((void *) local);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return pid;
|
return proc;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_pid
|
static ssize_t
|
||||||
adb_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) {
|
||||||
|
pipe_t pipe_stdout;
|
||||||
|
process_t proc = adb_execute_redirect(serial, adb_cmd, adb_cmd_len, NULL,
|
||||||
|
&pipe_stdout, NULL);
|
||||||
|
|
||||||
|
ssize_t r = read_pipe_all(pipe_stdout, buf, buf_len);
|
||||||
|
close_pipe(pipe_stdout);
|
||||||
|
|
||||||
|
if (!process_check_success(proc, name, true)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
truncate_first_line(char *data, size_t len) {
|
||||||
|
data[len - 1] = '\0';
|
||||||
|
char *eol = strpbrk(data, "\r\n");
|
||||||
|
if (eol) {
|
||||||
|
*eol = '\0';
|
||||||
|
len = eol - data;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
adb_get_serialno(void) {
|
||||||
|
char buf[128];
|
||||||
|
|
||||||
|
const char *const adb_cmd[] = {"get-serialno"};
|
||||||
|
ssize_t r = adb_execute_for_output(NULL, adb_cmd, ARRAY_LEN(adb_cmd),
|
||||||
|
buf, sizeof(buf), "get-serialno");
|
||||||
|
if (r <= 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
truncate_first_line(buf, r);
|
||||||
|
return strdup(buf);
|
||||||
}
|
}
|
||||||
|
@ -8,35 +8,36 @@
|
|||||||
|
|
||||||
#include "util/process.h"
|
#include "util/process.h"
|
||||||
|
|
||||||
sc_pid
|
process_t
|
||||||
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
|
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
|
||||||
|
|
||||||
sc_pid
|
process_t
|
||||||
adb_execute_p(const char *serial, const char *const adb_cmd[], size_t len,
|
adb_execute_redirect(const char *serial, const char *const adb_cmd[],
|
||||||
sc_pipe *pout);
|
size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout,
|
||||||
|
pipe_t *pipe_stderr);
|
||||||
|
|
||||||
sc_pid
|
process_t
|
||||||
adb_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);
|
||||||
|
|
||||||
sc_pid
|
process_t
|
||||||
adb_forward_remove(const char *serial, uint16_t local_port);
|
adb_forward_remove(const char *serial, uint16_t local_port);
|
||||||
|
|
||||||
sc_pid
|
process_t
|
||||||
adb_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);
|
||||||
|
|
||||||
sc_pid
|
process_t
|
||||||
adb_reverse_remove(const char *serial, const char *device_socket_name);
|
adb_reverse_remove(const char *serial, const char *device_socket_name);
|
||||||
|
|
||||||
sc_pid
|
process_t
|
||||||
adb_push(const char *serial, const char *local, const char *remote);
|
adb_push(const char *serial, const char *local, const char *remote);
|
||||||
|
|
||||||
sc_pid
|
process_t
|
||||||
adb_install(const char *serial, const char *local);
|
adb_install(const char *serial, const char *local);
|
||||||
|
|
||||||
// Execute "adb get-serialno" and give a pipe to get the result
|
// Return the result of "adb get-serialno".
|
||||||
sc_pid
|
char *
|
||||||
adb_get_serialno(sc_pipe *pout);
|
adb_get_serialno(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,192 +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
|
|
||||||
enable_tunnel_reverse(struct sc_intr *intr, const char *serial,
|
|
||||||
uint16_t local_port) {
|
|
||||||
sc_pid pid = adb_reverse(serial, SC_SOCKET_NAME, local_port);
|
|
||||||
return sc_process_check_success_intr(intr, pid, "adb reverse");
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
disable_tunnel_reverse(struct sc_intr *intr, const char *serial) {
|
|
||||||
sc_pid pid = adb_reverse_remove(serial, SC_SOCKET_NAME);
|
|
||||||
return sc_process_check_success_intr(intr, pid, "adb reverse --remove");
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
enable_tunnel_forward(struct sc_intr *intr, const char *serial,
|
|
||||||
uint16_t local_port) {
|
|
||||||
sc_pid pid = adb_forward(serial, local_port, SC_SOCKET_NAME);
|
|
||||||
return sc_process_check_success_intr(intr, pid, "adb forward");
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
disable_tunnel_forward(struct sc_intr *intr, const char *serial,
|
|
||||||
uint16_t local_port) {
|
|
||||||
sc_pid pid = adb_forward_remove(serial, local_port);
|
|
||||||
return sc_process_check_success_intr(intr, pid, "adb forward --remove");
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (!enable_tunnel_reverse(intr, 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.
|
|
||||||
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 (!disable_tunnel_reverse(intr, 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 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 (enable_tunnel_forward(intr, serial, port)) {
|
|
||||||
// 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 = disable_tunnel_forward(intr, serial, tunnel->local_port);
|
|
||||||
} else {
|
|
||||||
ret = disable_tunnel_reverse(intr, serial);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
@ -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
|
|
1197
app/src/cli.c
1197
app/src/cli.c
File diff suppressed because it is too large
Load Diff
@ -26,7 +26,7 @@ struct sc_clock_point {
|
|||||||
* array.
|
* array.
|
||||||
*
|
*
|
||||||
* To estimate the slope, it splits the last SC_CLOCK_RANGE points into two
|
* To estimate the slope, it splits the last SC_CLOCK_RANGE points into two
|
||||||
* sets of SC_CLOCK_RANGE/2 points, and computes their centroid ("average
|
* sets of SC_CLOCK_RANGE/2 points, and compute their centroid ("average
|
||||||
* point"). The slope of the estimated affine function is that of the line
|
* point"). The slope of the estimated affine function is that of the line
|
||||||
* passing through these two points.
|
* passing through these two points.
|
||||||
*
|
*
|
||||||
|
@ -43,11 +43,6 @@
|
|||||||
# define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
|
# define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 6)
|
|
||||||
// <https://github.com/libsdl-org/SDL/commit/d7a318de563125e5bb465b1000d6bc9576fbc6fc>
|
|
||||||
# define SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 8)
|
#if SDL_VERSION_ATLEAST(2, 0, 8)
|
||||||
// <https://hg.libsdl.org/SDL/rev/dfde5d3f9781>
|
// <https://hg.libsdl.org/SDL/rev/dfde5d3f9781>
|
||||||
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
|
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#include "util/buffer_util.h"
|
#include "util/buffer_util.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map an enum value to a string based on an array, without crashing on an
|
* Map an enum value to a string based on an array, without crashing on an
|
||||||
@ -56,7 +56,7 @@ static const char *const screen_power_mode_labels[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
write_position(uint8_t *buf, const struct sc_position *position) {
|
write_position(uint8_t *buf, const struct position *position) {
|
||||||
buffer_write32be(&buf[0], position->point.x);
|
buffer_write32be(&buf[0], position->point.x);
|
||||||
buffer_write32be(&buf[4], position->point.y);
|
buffer_write32be(&buf[4], position->point.y);
|
||||||
buffer_write16be(&buf[8], position->screen_size.width);
|
buffer_write16be(&buf[8], position->screen_size.width);
|
||||||
@ -66,7 +66,7 @@ write_position(uint8_t *buf, const struct sc_position *position) {
|
|||||||
// write length (2 bytes) + string (non nul-terminated)
|
// write length (2 bytes) + string (non nul-terminated)
|
||||||
static size_t
|
static size_t
|
||||||
write_string(const char *utf8, size_t max_len, unsigned char *buf) {
|
write_string(const char *utf8, size_t max_len, unsigned char *buf) {
|
||||||
size_t len = sc_str_utf8_truncation_index(utf8, max_len);
|
size_t len = utf8_truncation_index(utf8, max_len);
|
||||||
buffer_write32be(buf, len);
|
buffer_write32be(buf, len);
|
||||||
memcpy(&buf[4], utf8, len);
|
memcpy(&buf[4], utf8, len);
|
||||||
return 4 + len;
|
return 4 + len;
|
||||||
|
@ -57,11 +57,11 @@ struct control_msg {
|
|||||||
enum android_motionevent_action action;
|
enum android_motionevent_action action;
|
||||||
enum android_motionevent_buttons buttons;
|
enum android_motionevent_buttons buttons;
|
||||||
uint64_t pointer_id;
|
uint64_t pointer_id;
|
||||||
struct sc_position position;
|
struct position position;
|
||||||
float pressure;
|
float pressure;
|
||||||
} inject_touch_event;
|
} inject_touch_event;
|
||||||
struct {
|
struct {
|
||||||
struct sc_position position;
|
struct position position;
|
||||||
int32_t hscroll;
|
int32_t hscroll;
|
||||||
int32_t vscroll;
|
int32_t vscroll;
|
||||||
} inject_scroll_event;
|
} inject_scroll_event;
|
||||||
|
@ -3,22 +3,22 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
struct sc_size {
|
struct size {
|
||||||
uint16_t width;
|
uint16_t width;
|
||||||
uint16_t height;
|
uint16_t height;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_point {
|
struct point {
|
||||||
int32_t x;
|
int32_t x;
|
||||||
int32_t y;
|
int32_t y;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_position {
|
struct position {
|
||||||
// The video screen size may be different from the real device screen size,
|
// The video screen size may be different from the real device screen size,
|
||||||
// so store to which size the absolute position apply, to scale it
|
// so store to which size the absolute position apply, to scale it
|
||||||
// accordingly.
|
// accordingly.
|
||||||
struct sc_size screen_size;
|
struct size screen_size;
|
||||||
struct sc_point point;
|
struct point point;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -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)
|
|
||||||
|
@ -46,7 +46,7 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
|
|||||||
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->current_process = PROCESS_NONE;
|
||||||
|
|
||||||
file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET;
|
file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET;
|
||||||
|
|
||||||
@ -65,12 +65,12 @@ file_handler_destroy(struct file_handler *file_handler) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static sc_pid
|
static process_t
|
||||||
install_apk(const char *serial, const char *file) {
|
install_apk(const char *serial, const char *file) {
|
||||||
return adb_install(serial, file);
|
return adb_install(serial, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
static sc_pid
|
static process_t
|
||||||
push_file(const char *serial, const char *file, const char *push_target) {
|
push_file(const char *serial, const char *file, const char *push_target) {
|
||||||
return adb_push(serial, file, push_target);
|
return adb_push(serial, file, push_target);
|
||||||
}
|
}
|
||||||
@ -109,7 +109,7 @@ run_file_handler(void *data) {
|
|||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
sc_mutex_lock(&file_handler->mutex);
|
sc_mutex_lock(&file_handler->mutex);
|
||||||
file_handler->current_process = SC_PROCESS_NONE;
|
file_handler->current_process = PROCESS_NONE;
|
||||||
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
|
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
|
||||||
sc_cond_wait(&file_handler->event_cond, &file_handler->mutex);
|
sc_cond_wait(&file_handler->event_cond, &file_handler->mutex);
|
||||||
}
|
}
|
||||||
@ -123,26 +123,26 @@ run_file_handler(void *data) {
|
|||||||
assert(non_empty);
|
assert(non_empty);
|
||||||
(void) non_empty;
|
(void) non_empty;
|
||||||
|
|
||||||
sc_pid pid;
|
process_t process;
|
||||||
if (req.action == ACTION_INSTALL_APK) {
|
if (req.action == ACTION_INSTALL_APK) {
|
||||||
LOGI("Installing %s...", req.file);
|
LOGI("Installing %s...", req.file);
|
||||||
pid = install_apk(file_handler->serial, req.file);
|
process = install_apk(file_handler->serial, req.file);
|
||||||
} else {
|
} else {
|
||||||
LOGI("Pushing %s...", req.file);
|
LOGI("Pushing %s...", req.file);
|
||||||
pid = push_file(file_handler->serial, req.file,
|
process = push_file(file_handler->serial, req.file,
|
||||||
file_handler->push_target);
|
file_handler->push_target);
|
||||||
}
|
}
|
||||||
file_handler->current_process = pid;
|
file_handler->current_process = process;
|
||||||
sc_mutex_unlock(&file_handler->mutex);
|
sc_mutex_unlock(&file_handler->mutex);
|
||||||
|
|
||||||
if (req.action == ACTION_INSTALL_APK) {
|
if (req.action == ACTION_INSTALL_APK) {
|
||||||
if (sc_process_check_success(pid, "adb install", false)) {
|
if (process_check_success(process, "adb install", false)) {
|
||||||
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 {
|
||||||
if (sc_process_check_success(pid, "adb push", false)) {
|
if (process_check_success(process, "adb push", false)) {
|
||||||
LOGI("%s successfully pushed to %s", req.file,
|
LOGI("%s successfully pushed to %s", req.file,
|
||||||
file_handler->push_target);
|
file_handler->push_target);
|
||||||
} else {
|
} else {
|
||||||
@ -152,11 +152,11 @@ run_file_handler(void *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sc_mutex_lock(&file_handler->mutex);
|
sc_mutex_lock(&file_handler->mutex);
|
||||||
// Close the process (it is necessarily already terminated)
|
// Close the process (it is necessary already terminated)
|
||||||
// Execute this call with mutex locked to avoid race conditions with
|
// Execute this call with mutex locked to avoid race conditions with
|
||||||
// file_handler_stop()
|
// file_handler_stop()
|
||||||
sc_process_close(file_handler->current_process);
|
process_close(file_handler->current_process);
|
||||||
file_handler->current_process = SC_PROCESS_NONE;
|
file_handler->current_process = PROCESS_NONE;
|
||||||
sc_mutex_unlock(&file_handler->mutex);
|
sc_mutex_unlock(&file_handler->mutex);
|
||||||
|
|
||||||
file_handler_request_destroy(&req);
|
file_handler_request_destroy(&req);
|
||||||
@ -183,8 +183,8 @@ 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);
|
||||||
if (file_handler->current_process != SC_PROCESS_NONE) {
|
if (file_handler->current_process != PROCESS_NONE) {
|
||||||
if (!sc_process_terminate(file_handler->current_process)) {
|
if (!process_terminate(file_handler->current_process)) {
|
||||||
LOGW("Could not terminate push/install process");
|
LOGW("Could not terminate push/install process");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ struct file_handler {
|
|||||||
sc_cond event_cond;
|
sc_cond event_cond;
|
||||||
bool stopped;
|
bool stopped;
|
||||||
bool initialized;
|
bool initialized;
|
||||||
sc_pid current_process;
|
process_t current_process;
|
||||||
struct file_handler_request_queue queue;
|
struct file_handler_request_queue queue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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)) {
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "util/file.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str.h"
|
#include "util/process.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
|
||||||
@ -46,7 +46,7 @@ get_icon_path(void) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
char *icon_path = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME);
|
char *icon_path = get_local_file_path(SCRCPY_PORTABLE_ICON_FILENAME);
|
||||||
if (!icon_path) {
|
if (!icon_path) {
|
||||||
LOGE("Could not get icon path");
|
LOGE("Could not get icon path");
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -332,7 +332,7 @@ input_manager_process_text_input(struct input_manager *im,
|
|||||||
static bool
|
static bool
|
||||||
simulate_virtual_finger(struct input_manager *im,
|
simulate_virtual_finger(struct input_manager *im,
|
||||||
enum android_motionevent_action action,
|
enum android_motionevent_action action,
|
||||||
struct sc_point point) {
|
struct point point) {
|
||||||
bool up = action == AMOTION_EVENT_ACTION_UP;
|
bool up = action == AMOTION_EVENT_ACTION_UP;
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
@ -352,8 +352,8 @@ simulate_virtual_finger(struct input_manager *im,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct sc_point
|
static struct point
|
||||||
inverse_point(struct sc_point point, struct sc_size size) {
|
inverse_point(struct point point, struct size size) {
|
||||||
point.x = size.width - point.x;
|
point.x = size.width - point.x;
|
||||||
point.y = size.height - point.y;
|
point.y = size.height - point.y;
|
||||||
return point;
|
return point;
|
||||||
@ -545,10 +545,10 @@ input_manager_process_mouse_motion(struct input_manager *im,
|
|||||||
im->mp->ops->process_mouse_motion(im->mp, event);
|
im->mp->ops->process_mouse_motion(im->mp, event);
|
||||||
|
|
||||||
if (im->vfinger_down) {
|
if (im->vfinger_down) {
|
||||||
struct sc_point mouse =
|
struct point mouse =
|
||||||
screen_convert_window_to_frame_coords(im->screen, event->x,
|
screen_convert_window_to_frame_coords(im->screen, event->x,
|
||||||
event->y);
|
event->y);
|
||||||
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
|
struct point vfinger = inverse_point(mouse, im->screen->frame_size);
|
||||||
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
|
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -630,10 +630,10 @@ input_manager_process_mouse_button(struct input_manager *im,
|
|||||||
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
|
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
|
||||||
if ((down && !im->vfinger_down && CTRL_PRESSED)
|
if ((down && !im->vfinger_down && CTRL_PRESSED)
|
||||||
|| (!down && im->vfinger_down)) {
|
|| (!down && im->vfinger_down)) {
|
||||||
struct sc_point mouse =
|
struct point mouse =
|
||||||
screen_convert_window_to_frame_coords(im->screen, event->x,
|
screen_convert_window_to_frame_coords(im->screen, event->x,
|
||||||
event->y);
|
event->y);
|
||||||
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
|
struct point vfinger = inverse_point(mouse, im->screen->frame_size);
|
||||||
enum android_motionevent_action action = down
|
enum android_motionevent_action action = down
|
||||||
? AMOTION_EVENT_ACTION_DOWN
|
? AMOTION_EVENT_ACTION_DOWN
|
||||||
: AMOTION_EVENT_ACTION_UP;
|
: AMOTION_EVENT_ACTION_UP;
|
||||||
|
@ -125,7 +125,7 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
|
|||||||
int mouse_y;
|
int mouse_y;
|
||||||
SDL_GetMouseState(&mouse_x, &mouse_y);
|
SDL_GetMouseState(&mouse_x, &mouse_y);
|
||||||
|
|
||||||
struct sc_position position = {
|
struct position position = {
|
||||||
.screen_size = screen->frame_size,
|
.screen_size = screen->frame_size,
|
||||||
.point = screen_convert_window_to_frame_coords(screen,
|
.point = screen_convert_window_to_frame_coords(screen,
|
||||||
mouse_x, mouse_y),
|
mouse_x, mouse_y),
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
#include <libavutil/time.h>
|
#include <libavutil/time.h>
|
||||||
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
/** Downcast packet_sink to recorder */
|
/** Downcast packet_sink to recorder */
|
||||||
#define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink)
|
#define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink)
|
||||||
@ -26,7 +26,7 @@ find_muxer(const char *name) {
|
|||||||
oformat = av_oformat_next(oformat);
|
oformat = av_oformat_next(oformat);
|
||||||
#endif
|
#endif
|
||||||
// until null or containing the requested name
|
// until null or containing the requested name
|
||||||
} while (oformat && !sc_str_list_contains(oformat->name, ',', name));
|
} while (oformat && !strlist_contains(oformat->name, ',', name));
|
||||||
return oformat;
|
return oformat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,7 +372,7 @@ bool
|
|||||||
recorder_init(struct recorder *recorder,
|
recorder_init(struct recorder *recorder,
|
||||||
const char *filename,
|
const char *filename,
|
||||||
enum sc_record_format format,
|
enum sc_record_format format,
|
||||||
struct sc_size declared_frame_size) {
|
struct size declared_frame_size) {
|
||||||
recorder->filename = strdup(filename);
|
recorder->filename = strdup(filename);
|
||||||
if (!recorder->filename) {
|
if (!recorder->filename) {
|
||||||
LOGE("Could not strdup filename");
|
LOGE("Could not strdup filename");
|
||||||
|
@ -25,7 +25,7 @@ struct recorder {
|
|||||||
char *filename;
|
char *filename;
|
||||||
enum sc_record_format format;
|
enum sc_record_format format;
|
||||||
AVFormatContext *ctx;
|
AVFormatContext *ctx;
|
||||||
struct sc_size declared_frame_size;
|
struct size declared_frame_size;
|
||||||
bool header_written;
|
bool header_written;
|
||||||
|
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
@ -44,7 +44,7 @@ struct recorder {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
recorder_init(struct recorder *recorder, const char *filename,
|
recorder_init(struct recorder *recorder, const char *filename,
|
||||||
enum sc_record_format format, struct sc_size declared_frame_size);
|
enum sc_record_format format, struct size declared_frame_size);
|
||||||
|
|
||||||
void
|
void
|
||||||
recorder_destroy(struct recorder *recorder);
|
recorder_destroy(struct recorder *recorder);
|
||||||
|
195
app/src/scrcpy.c
195
app/src/scrcpy.c
@ -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;
|
||||||
@ -57,30 +57,41 @@ struct scrcpy {
|
|||||||
struct input_manager input_manager;
|
struct input_manager input_manager;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline void
|
|
||||||
push_event(uint32_t type, const char *name) {
|
|
||||||
SDL_Event event;
|
|
||||||
event.type = type;
|
|
||||||
int ret = SDL_PushEvent(&event);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("Could not post %s event: %s", name, SDL_GetError());
|
|
||||||
// What could we do?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#define PUSH_EVENT(TYPE) push_event(TYPE, # TYPE)
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
|
BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
|
||||||
if (ctrl_type == CTRL_C_EVENT) {
|
if (ctrl_type == CTRL_C_EVENT) {
|
||||||
PUSH_EVENT(SDL_QUIT);
|
SDL_Event event;
|
||||||
|
event.type = SDL_QUIT;
|
||||||
|
SDL_PushEvent(&event);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|
||||||
static void
|
// init SDL and set appropriate hints
|
||||||
sdl_set_hints(const char *render_driver) {
|
static bool
|
||||||
|
sdl_init_and_configure(bool display, const char *render_driver,
|
||||||
|
bool disable_screensaver) {
|
||||||
|
uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS;
|
||||||
|
if (SDL_Init(flags)) {
|
||||||
|
LOGC("Could not initialize SDL: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
atexit(SDL_Quit);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// Clean up properly on Ctrl+C on Windows
|
||||||
|
bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE);
|
||||||
|
if (!ok) {
|
||||||
|
LOGW("Could not set Ctrl+C handler");
|
||||||
|
}
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
|
if (!display) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) {
|
if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) {
|
||||||
LOGW("Could not set render driver");
|
LOGW("Could not set render driver");
|
||||||
@ -98,15 +109,6 @@ sdl_set_hints(const char *render_driver) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef SCRCPY_SDL_HAS_HINT_TOUCH_MOUSE_EVENTS
|
|
||||||
// Disable synthetic mouse events from touch events
|
|
||||||
// Touch events with id SDL_TOUCH_MOUSEID are ignored anyway, but it is
|
|
||||||
// better not to generate them in the first place.
|
|
||||||
if (!SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0")) {
|
|
||||||
LOGW("Could not disable synthetic mouse events");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
|
#ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
|
||||||
// Disable compositor bypassing on X11
|
// Disable compositor bypassing on X11
|
||||||
if (!SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0")) {
|
if (!SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0")) {
|
||||||
@ -118,21 +120,6 @@ sdl_set_hints(const char *render_driver) {
|
|||||||
if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
|
if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
|
||||||
LOGW("Could not disable minimize on focus loss");
|
LOGW("Could not disable minimize on focus loss");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sdl_configure(bool display, bool disable_screensaver) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
// Clean up properly on Ctrl+C on Windows
|
|
||||||
bool ok = SetConsoleCtrlHandler(windows_ctrl_handler, TRUE);
|
|
||||||
if (!ok) {
|
|
||||||
LOGW("Could not set Ctrl+C handler");
|
|
||||||
}
|
|
||||||
#endif // _WIN32
|
|
||||||
|
|
||||||
if (!display) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (disable_screensaver) {
|
if (disable_screensaver) {
|
||||||
LOGD("Screensaver disabled");
|
LOGD("Screensaver disabled");
|
||||||
@ -141,6 +128,8 @@ sdl_configure(bool display, bool disable_screensaver) {
|
|||||||
LOGD("Screensaver enabled");
|
LOGD("Screensaver enabled");
|
||||||
SDL_EnableScreenSaver();
|
SDL_EnableScreenSaver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@ -217,29 +206,6 @@ event_loop(struct scrcpy *s, const struct scrcpy_options *options) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
await_for_server(void) {
|
|
||||||
SDL_Event event;
|
|
||||||
while (SDL_WaitEvent(&event)) {
|
|
||||||
switch (event.type) {
|
|
||||||
case SDL_QUIT:
|
|
||||||
LOGD("User requested to quit");
|
|
||||||
return false;
|
|
||||||
case EVENT_SERVER_CONNECTION_FAILED:
|
|
||||||
LOGE("Server connection failed");
|
|
||||||
return false;
|
|
||||||
case EVENT_SERVER_CONNECTED:
|
|
||||||
LOGD("Server connected");
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGE("SDL_WaitEvent() error: %s", SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static SDL_LogPriority
|
static SDL_LogPriority
|
||||||
sdl_priority_from_av_level(int level) {
|
sdl_priority_from_av_level(int level) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
@ -282,33 +248,9 @@ stream_on_eos(struct stream *stream, void *userdata) {
|
|||||||
(void) stream;
|
(void) stream;
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
PUSH_EVENT(EVENT_STREAM_STOPPED);
|
SDL_Event stop_event;
|
||||||
}
|
stop_event.type = EVENT_STREAM_STOPPED;
|
||||||
|
SDL_PushEvent(&stop_event);
|
||||||
static void
|
|
||||||
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
|
||||||
@ -316,14 +258,6 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
static struct scrcpy scrcpy;
|
static struct scrcpy scrcpy;
|
||||||
struct scrcpy *s = &scrcpy;
|
struct scrcpy *s = &scrcpy;
|
||||||
|
|
||||||
// Minimal SDL initialization
|
|
||||||
if (SDL_Init(SDL_INIT_EVENTS)) {
|
|
||||||
LOGC("Could not initialize SDL: %s", SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
atexit(SDL_Quit);
|
|
||||||
|
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
bool server_started = false;
|
bool server_started = false;
|
||||||
@ -340,7 +274,8 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
bool controller_started = false;
|
bool controller_started = false;
|
||||||
bool screen_initialized = false;
|
bool screen_initialized = false;
|
||||||
|
|
||||||
struct sc_server_params params = {
|
bool record = !!options->record_filename;
|
||||||
|
struct server_params params = {
|
||||||
.serial = options->serial,
|
.serial = options->serial,
|
||||||
.log_level = options->log_level,
|
.log_level = options->log_level,
|
||||||
.crop = options->crop,
|
.crop = options->crop,
|
||||||
@ -359,46 +294,30 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.power_off_on_close = options->power_off_on_close,
|
.power_off_on_close = options->power_off_on_close,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct sc_server_callbacks cbs = {
|
if (!server_init(&s->server, ¶ms)) {
|
||||||
.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, ¶ms, &cbs, NULL)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_server_start(&s->server)) {
|
if (!server_start(&s->server)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
server_started = true;
|
server_started = true;
|
||||||
|
|
||||||
if (options->display) {
|
if (!sdl_init_and_configure(options->display, options->render_driver,
|
||||||
sdl_set_hints(options->render_driver);
|
options->disable_screensaver)) {
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize SDL video in addition if display is enabled
|
|
||||||
if (options->display && SDL_Init(SDL_INIT_VIDEO)) {
|
|
||||||
LOGC("Could not initialize SDL: %s", SDL_GetError());
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
sdl_configure(options->display, options->disable_screensaver);
|
char device_name[DEVICE_NAME_FIELD_LENGTH];
|
||||||
|
struct size frame_size;
|
||||||
|
|
||||||
// Await for server without blocking Ctrl+C handling
|
if (!server_connect_to(&s->server, device_name, &frame_size)) {
|
||||||
if (!await_for_server()) {
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
// It is necessarily initialized here, since the device is connected
|
|
||||||
struct sc_server_info *info = &s->server.info;
|
|
||||||
|
|
||||||
const char *serial = s->server.params.serial;
|
|
||||||
assert(serial);
|
|
||||||
|
|
||||||
if (options->display && options->control) {
|
if (options->display && options->control) {
|
||||||
if (!file_handler_init(&s->file_handler, serial,
|
if (!file_handler_init(&s->file_handler, options->serial,
|
||||||
options->push_target)) {
|
options->push_target)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
@ -416,11 +335,11 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct recorder *rec = NULL;
|
struct recorder *rec = NULL;
|
||||||
if (options->record_filename) {
|
if (record) {
|
||||||
if (!recorder_init(&s->recorder,
|
if (!recorder_init(&s->recorder,
|
||||||
options->record_filename,
|
options->record_filename,
|
||||||
options->record_format,
|
options->record_format,
|
||||||
info->frame_size)) {
|
frame_size)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
rec = &s->recorder;
|
rec = &s->recorder;
|
||||||
@ -466,11 +385,11 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
|
|
||||||
if (options->display) {
|
if (options->display) {
|
||||||
const char *window_title =
|
const char *window_title =
|
||||||
options->window_title ? options->window_title : info->device_name;
|
options->window_title ? options->window_title : device_name;
|
||||||
|
|
||||||
struct screen_params screen_params = {
|
struct screen_params screen_params = {
|
||||||
.window_title = window_title,
|
.window_title = window_title,
|
||||||
.frame_size = info->frame_size,
|
.frame_size = frame_size,
|
||||||
.always_on_top = options->always_on_top,
|
.always_on_top = options->always_on_top,
|
||||||
.window_x = options->window_x,
|
.window_x = options->window_x,
|
||||||
.window_y = options->window_y,
|
.window_y = options->window_y,
|
||||||
@ -493,8 +412,8 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
if (options->v4l2_device) {
|
if (options->v4l2_device) {
|
||||||
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device,
|
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, frame_size,
|
||||||
info->frame_size, options->v4l2_buffer)) {
|
options->v4l2_buffer)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,7 +438,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;
|
||||||
}
|
}
|
||||||
@ -597,7 +530,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
|
||||||
@ -642,7 +575,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;
|
||||||
}
|
}
|
||||||
|
176
app/src/screen.c
176
app/src/screen.c
@ -14,9 +14,9 @@
|
|||||||
|
|
||||||
#define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink)
|
#define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink)
|
||||||
|
|
||||||
static inline struct sc_size
|
static inline struct size
|
||||||
get_rotated_size(struct sc_size size, int rotation) {
|
get_rotated_size(struct size size, int rotation) {
|
||||||
struct sc_size rotated_size;
|
struct size rotated_size;
|
||||||
if (rotation & 1) {
|
if (rotation & 1) {
|
||||||
rotated_size.width = size.height;
|
rotated_size.width = size.height;
|
||||||
rotated_size.height = size.width;
|
rotated_size.height = size.width;
|
||||||
@ -27,26 +27,26 @@ get_rotated_size(struct sc_size size, int rotation) {
|
|||||||
return rotated_size;
|
return rotated_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the window size in a struct sc_size
|
// get the window size in a struct size
|
||||||
static struct sc_size
|
static struct size
|
||||||
get_window_size(const struct screen *screen) {
|
get_window_size(const struct screen *screen) {
|
||||||
int width;
|
int width;
|
||||||
int height;
|
int height;
|
||||||
SDL_GetWindowSize(screen->window, &width, &height);
|
SDL_GetWindowSize(screen->window, &width, &height);
|
||||||
|
|
||||||
struct sc_size size;
|
struct size size;
|
||||||
size.width = width;
|
size.width = width;
|
||||||
size.height = height;
|
size.height = height;
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct sc_point
|
static struct point
|
||||||
get_window_position(const struct screen *screen) {
|
get_window_position(const struct screen *screen) {
|
||||||
int x;
|
int x;
|
||||||
int y;
|
int y;
|
||||||
SDL_GetWindowPosition(screen->window, &x, &y);
|
SDL_GetWindowPosition(screen->window, &x, &y);
|
||||||
|
|
||||||
struct sc_point point;
|
struct point point;
|
||||||
point.x = x;
|
point.x = x;
|
||||||
point.y = y;
|
point.y = y;
|
||||||
return point;
|
return point;
|
||||||
@ -54,7 +54,7 @@ get_window_position(const struct screen *screen) {
|
|||||||
|
|
||||||
// set the window size to be applied when fullscreen is disabled
|
// set the window size to be applied when fullscreen is disabled
|
||||||
static void
|
static void
|
||||||
set_window_size(struct screen *screen, struct sc_size new_size) {
|
set_window_size(struct screen *screen, struct size new_size) {
|
||||||
assert(!screen->fullscreen);
|
assert(!screen->fullscreen);
|
||||||
assert(!screen->maximized);
|
assert(!screen->maximized);
|
||||||
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
|
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
|
||||||
@ -62,7 +62,7 @@ set_window_size(struct screen *screen, struct sc_size new_size) {
|
|||||||
|
|
||||||
// get the preferred display bounds (i.e. the screen bounds with some margins)
|
// get the preferred display bounds (i.e. the screen bounds with some margins)
|
||||||
static bool
|
static bool
|
||||||
get_preferred_display_bounds(struct sc_size *bounds) {
|
get_preferred_display_bounds(struct size *bounds) {
|
||||||
SDL_Rect rect;
|
SDL_Rect rect;
|
||||||
#ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
|
#ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
|
||||||
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r))
|
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r))
|
||||||
@ -80,7 +80,7 @@ get_preferred_display_bounds(struct sc_size *bounds) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
is_optimal_size(struct sc_size current_size, struct sc_size content_size) {
|
is_optimal_size(struct size current_size, struct size content_size) {
|
||||||
// The size is optimal if we can recompute one dimension of the current
|
// The size is optimal if we can recompute one dimension of the current
|
||||||
// size from the other
|
// size from the other
|
||||||
return current_size.height == current_size.width * content_size.height
|
return current_size.height == current_size.width * content_size.height
|
||||||
@ -94,16 +94,16 @@ is_optimal_size(struct sc_size current_size, struct sc_size content_size) {
|
|||||||
// crops the black borders)
|
// crops the black borders)
|
||||||
// - it keeps the aspect ratio
|
// - it keeps the aspect ratio
|
||||||
// - it scales down to make it fit in the display_size
|
// - it scales down to make it fit in the display_size
|
||||||
static struct sc_size
|
static struct size
|
||||||
get_optimal_size(struct sc_size current_size, struct sc_size content_size) {
|
get_optimal_size(struct size current_size, struct size content_size) {
|
||||||
if (content_size.width == 0 || content_size.height == 0) {
|
if (content_size.width == 0 || content_size.height == 0) {
|
||||||
// avoid division by 0
|
// avoid division by 0
|
||||||
return current_size;
|
return current_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_size window_size;
|
struct size window_size;
|
||||||
|
|
||||||
struct sc_size display_size;
|
struct size display_size;
|
||||||
if (!get_preferred_display_bounds(&display_size)) {
|
if (!get_preferred_display_bounds(&display_size)) {
|
||||||
// could not get display bounds, do not constraint the size
|
// could not get display bounds, do not constraint the size
|
||||||
window_size.width = current_size.width;
|
window_size.width = current_size.width;
|
||||||
@ -135,10 +135,10 @@ get_optimal_size(struct sc_size current_size, struct sc_size content_size) {
|
|||||||
|
|
||||||
// initially, there is no current size, so use the frame size as current size
|
// initially, there is no current size, so use the frame size as current size
|
||||||
// req_width and req_height, if not 0, are the sizes requested by the user
|
// req_width and req_height, if not 0, are the sizes requested by the user
|
||||||
static inline struct sc_size
|
static inline struct size
|
||||||
get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
|
get_initial_optimal_size(struct size content_size, uint16_t req_width,
|
||||||
uint16_t req_height) {
|
uint16_t req_height) {
|
||||||
struct sc_size window_size;
|
struct size window_size;
|
||||||
if (!req_width && !req_height) {
|
if (!req_width && !req_height) {
|
||||||
window_size = get_optimal_size(content_size, content_size);
|
window_size = get_optimal_size(content_size, content_size);
|
||||||
} else {
|
} else {
|
||||||
@ -166,9 +166,9 @@ screen_update_content_rect(struct screen *screen) {
|
|||||||
int dh;
|
int dh;
|
||||||
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
||||||
|
|
||||||
struct sc_size content_size = screen->content_size;
|
struct size content_size = screen->content_size;
|
||||||
// The drawable size is the window size * the HiDPI scale
|
// The drawable size is the window size * the HiDPI scale
|
||||||
struct sc_size drawable_size = {dw, dh};
|
struct size drawable_size = {dw, dh};
|
||||||
|
|
||||||
SDL_Rect *rect = &screen->rect;
|
SDL_Rect *rect = &screen->rect;
|
||||||
|
|
||||||
@ -200,7 +200,7 @@ screen_update_content_rect(struct screen *screen) {
|
|||||||
static inline SDL_Texture *
|
static inline SDL_Texture *
|
||||||
create_texture(struct screen *screen) {
|
create_texture(struct screen *screen) {
|
||||||
SDL_Renderer *renderer = screen->renderer;
|
SDL_Renderer *renderer = screen->renderer;
|
||||||
struct sc_size size = screen->frame_size;
|
struct size size = screen->frame_size;
|
||||||
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
|
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
|
||||||
SDL_TEXTUREACCESS_STREAMING,
|
SDL_TEXTUREACCESS_STREAMING,
|
||||||
size.width, size.height);
|
size.width, size.height);
|
||||||
@ -224,45 +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 = ▭
|
|
||||||
} else {
|
|
||||||
assert(screen->rotation == 2);
|
|
||||||
dstrect = &screen->rect;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
|
|
||||||
angle, NULL, 0);
|
|
||||||
}
|
|
||||||
SDL_RenderPresent(screen->renderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#if defined(__APPLE__) || defined(__WINDOWS__)
|
#if defined(__APPLE__) || defined(__WINDOWS__)
|
||||||
# define CONTINUOUS_RESIZING_WORKAROUND
|
# define CONTINUOUS_RESIZING_WORKAROUND
|
||||||
#endif
|
#endif
|
||||||
@ -321,33 +282,17 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
|
|||||||
(void) vb;
|
(void) vb;
|
||||||
struct screen *screen = userdata;
|
struct screen *screen = userdata;
|
||||||
|
|
||||||
// event_failed implies previous_skipped (the previous frame may not have
|
|
||||||
// been consumed if the event was not sent)
|
|
||||||
assert(!screen->event_failed || previous_skipped);
|
|
||||||
|
|
||||||
bool need_new_event;
|
|
||||||
if (previous_skipped) {
|
if (previous_skipped) {
|
||||||
fps_counter_add_skipped_frame(&screen->fps_counter);
|
fps_counter_add_skipped_frame(&screen->fps_counter);
|
||||||
// The EVENT_NEW_FRAME triggered for the previous frame will consume
|
// The EVENT_NEW_FRAME triggered for the previous frame will consume
|
||||||
// this new frame instead, unless the previous event failed
|
// this new frame instead
|
||||||
need_new_event = screen->event_failed;
|
|
||||||
} else {
|
} else {
|
||||||
need_new_event = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (need_new_event) {
|
|
||||||
static SDL_Event new_frame_event = {
|
static SDL_Event new_frame_event = {
|
||||||
.type = EVENT_NEW_FRAME,
|
.type = EVENT_NEW_FRAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Post the event on the UI thread
|
// Post the event on the UI thread
|
||||||
int ret = SDL_PushEvent(&new_frame_event);
|
SDL_PushEvent(&new_frame_event);
|
||||||
if (ret < 0) {
|
|
||||||
LOGW("Could not post new frame event: %s", SDL_GetError());
|
|
||||||
screen->event_failed = true;
|
|
||||||
} else {
|
|
||||||
screen->event_failed = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,7 +302,6 @@ screen_init(struct screen *screen, const struct screen_params *params) {
|
|||||||
screen->has_frame = false;
|
screen->has_frame = false;
|
||||||
screen->fullscreen = false;
|
screen->fullscreen = false;
|
||||||
screen->maximized = false;
|
screen->maximized = false;
|
||||||
screen->event_failed = false;
|
|
||||||
|
|
||||||
static const struct sc_video_buffer_callbacks cbs = {
|
static const struct sc_video_buffer_callbacks cbs = {
|
||||||
.on_new_frame = sc_video_buffer_on_new_frame,
|
.on_new_frame = sc_video_buffer_on_new_frame,
|
||||||
@ -386,13 +330,13 @@ screen_init(struct screen *screen, const struct screen_params *params) {
|
|||||||
if (screen->rotation) {
|
if (screen->rotation) {
|
||||||
LOGI("Initial display rotation set to %u", screen->rotation);
|
LOGI("Initial display rotation set to %u", screen->rotation);
|
||||||
}
|
}
|
||||||
struct sc_size content_size =
|
struct size content_size =
|
||||||
get_rotated_size(screen->frame_size, screen->rotation);
|
get_rotated_size(screen->frame_size, screen->rotation);
|
||||||
screen->content_size = content_size;
|
screen->content_size = content_size;
|
||||||
|
|
||||||
struct sc_size window_size =
|
struct size window_size = get_initial_optimal_size(content_size,
|
||||||
get_initial_optimal_size(content_size,params->window_width,
|
params->window_width,
|
||||||
params->window_height);
|
params->window_height);
|
||||||
uint32_t window_flags = SDL_WINDOW_HIDDEN
|
uint32_t window_flags = SDL_WINDOW_HIDDEN
|
||||||
| SDL_WINDOW_RESIZABLE
|
| SDL_WINDOW_RESIZABLE
|
||||||
| SDL_WINDOW_ALLOW_HIGHDPI;
|
| SDL_WINDOW_ALLOW_HIGHDPI;
|
||||||
@ -564,10 +508,10 @@ screen_destroy(struct screen *screen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
resize_for_content(struct screen *screen, struct sc_size old_content_size,
|
resize_for_content(struct screen *screen, struct size old_content_size,
|
||||||
struct sc_size new_content_size) {
|
struct size new_content_size) {
|
||||||
struct sc_size window_size = get_window_size(screen);
|
struct size window_size = get_window_size(screen);
|
||||||
struct sc_size target_size = {
|
struct size target_size = {
|
||||||
.width = (uint32_t) window_size.width * new_content_size.width
|
.width = (uint32_t) window_size.width * new_content_size.width
|
||||||
/ old_content_size.width,
|
/ old_content_size.width,
|
||||||
.height = (uint32_t) window_size.height * new_content_size.height
|
.height = (uint32_t) window_size.height * new_content_size.height
|
||||||
@ -578,7 +522,7 @@ resize_for_content(struct screen *screen, struct sc_size old_content_size,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
set_content_size(struct screen *screen, struct sc_size new_content_size) {
|
set_content_size(struct screen *screen, struct size new_content_size) {
|
||||||
if (!screen->fullscreen && !screen->maximized) {
|
if (!screen->fullscreen && !screen->maximized) {
|
||||||
resize_for_content(screen, screen->content_size, new_content_size);
|
resize_for_content(screen, screen->content_size, new_content_size);
|
||||||
} else if (!screen->resize_pending) {
|
} else if (!screen->resize_pending) {
|
||||||
@ -609,7 +553,7 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_size new_content_size =
|
struct size new_content_size =
|
||||||
get_rotated_size(screen->frame_size, rotation);
|
get_rotated_size(screen->frame_size, rotation);
|
||||||
|
|
||||||
set_content_size(screen, new_content_size);
|
set_content_size(screen, new_content_size);
|
||||||
@ -622,7 +566,7 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
|
|||||||
|
|
||||||
// recreate the texture and resize the window if the frame size has changed
|
// recreate the texture and resize the window if the frame size has changed
|
||||||
static bool
|
static bool
|
||||||
prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) {
|
prepare_for_frame(struct screen *screen, struct size new_frame_size) {
|
||||||
if (screen->frame_size.width != new_frame_size.width
|
if (screen->frame_size.width != new_frame_size.width
|
||||||
|| screen->frame_size.height != new_frame_size.height) {
|
|| screen->frame_size.height != new_frame_size.height) {
|
||||||
// frame dimension changed, destroy texture
|
// frame dimension changed, destroy texture
|
||||||
@ -630,7 +574,7 @@ prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) {
|
|||||||
|
|
||||||
screen->frame_size = new_frame_size;
|
screen->frame_size = new_frame_size;
|
||||||
|
|
||||||
struct sc_size new_content_size =
|
struct size new_content_size =
|
||||||
get_rotated_size(new_frame_size, screen->rotation);
|
get_rotated_size(new_frame_size, screen->rotation);
|
||||||
set_content_size(screen, new_content_size);
|
set_content_size(screen, new_content_size);
|
||||||
|
|
||||||
@ -671,7 +615,7 @@ screen_update_frame(struct screen *screen) {
|
|||||||
|
|
||||||
fps_counter_add_rendered_frame(&screen->fps_counter);
|
fps_counter_add_rendered_frame(&screen->fps_counter);
|
||||||
|
|
||||||
struct sc_size new_frame_size = {frame->width, frame->height};
|
struct size new_frame_size = {frame->width, frame->height};
|
||||||
if (!prepare_for_frame(screen, new_frame_size)) {
|
if (!prepare_for_frame(screen, new_frame_size)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -681,6 +625,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 = ▭
|
||||||
|
} else {
|
||||||
|
assert(screen->rotation == 2);
|
||||||
|
dstrect = &screen->rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
|
||||||
|
angle, NULL, 0);
|
||||||
|
}
|
||||||
|
SDL_RenderPresent(screen->renderer);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
screen_switch_fullscreen(struct screen *screen) {
|
screen_switch_fullscreen(struct screen *screen) {
|
||||||
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
|
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||||
@ -704,10 +682,10 @@ screen_resize_to_fit(struct screen *screen) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_point point = get_window_position(screen);
|
struct point point = get_window_position(screen);
|
||||||
struct sc_size window_size = get_window_size(screen);
|
struct size window_size = get_window_size(screen);
|
||||||
|
|
||||||
struct sc_size optimal_size =
|
struct size optimal_size =
|
||||||
get_optimal_size(window_size, screen->content_size);
|
get_optimal_size(window_size, screen->content_size);
|
||||||
|
|
||||||
// Center the window related to the device screen
|
// Center the window related to the device screen
|
||||||
@ -733,7 +711,7 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
|
|||||||
screen->maximized = false;
|
screen->maximized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_size content_size = screen->content_size;
|
struct size content_size = screen->content_size;
|
||||||
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
|
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
|
||||||
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
|
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
|
||||||
content_size.height);
|
content_size.height);
|
||||||
@ -788,7 +766,7 @@ screen_handle_event(struct screen *screen, SDL_Event *event) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_point
|
struct point
|
||||||
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
||||||
int32_t x, int32_t y) {
|
int32_t x, int32_t y) {
|
||||||
unsigned rotation = screen->rotation;
|
unsigned rotation = screen->rotation;
|
||||||
@ -802,7 +780,7 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
|
|||||||
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
|
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
|
||||||
|
|
||||||
// rotate
|
// rotate
|
||||||
struct sc_point result;
|
struct point result;
|
||||||
switch (rotation) {
|
switch (rotation) {
|
||||||
case 0:
|
case 0:
|
||||||
result.x = x;
|
result.x = x;
|
||||||
@ -825,7 +803,7 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_point
|
struct point
|
||||||
screen_convert_window_to_frame_coords(struct screen *screen,
|
screen_convert_window_to_frame_coords(struct screen *screen,
|
||||||
int32_t x, int32_t y) {
|
int32_t x, int32_t y) {
|
||||||
screen_hidpi_scale_coords(screen, &x, &y);
|
screen_hidpi_scale_coords(screen, &x, &y);
|
||||||
|
@ -27,13 +27,13 @@ struct screen {
|
|||||||
SDL_Renderer *renderer;
|
SDL_Renderer *renderer;
|
||||||
SDL_Texture *texture;
|
SDL_Texture *texture;
|
||||||
struct sc_opengl gl;
|
struct sc_opengl gl;
|
||||||
struct sc_size frame_size;
|
struct size frame_size;
|
||||||
struct sc_size content_size; // rotated frame_size
|
struct size content_size; // rotated frame_size
|
||||||
|
|
||||||
bool resize_pending; // resize requested while fullscreen or maximized
|
bool resize_pending; // resize requested while fullscreen or maximized
|
||||||
// The content size the last time the window was not maximized or
|
// The content size the last time the window was not maximized or
|
||||||
// fullscreen (meaningful only when resize_pending is true)
|
// fullscreen (meaningful only when resize_pending is true)
|
||||||
struct sc_size windowed_content_size;
|
struct size windowed_content_size;
|
||||||
|
|
||||||
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
|
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
|
||||||
unsigned rotation;
|
unsigned rotation;
|
||||||
@ -44,14 +44,12 @@ struct screen {
|
|||||||
bool maximized;
|
bool maximized;
|
||||||
bool mipmaps;
|
bool mipmaps;
|
||||||
|
|
||||||
bool event_failed; // in case SDL_PushEvent() returned an error
|
|
||||||
|
|
||||||
AVFrame *frame;
|
AVFrame *frame;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct screen_params {
|
struct screen_params {
|
||||||
const char *window_title;
|
const char *window_title;
|
||||||
struct sc_size frame_size;
|
struct size frame_size;
|
||||||
bool always_on_top;
|
bool always_on_top;
|
||||||
|
|
||||||
int16_t window_x;
|
int16_t window_x;
|
||||||
@ -93,6 +91,13 @@ screen_destroy(struct screen *screen);
|
|||||||
void
|
void
|
||||||
screen_hide_window(struct screen *screen);
|
screen_hide_window(struct screen *screen);
|
||||||
|
|
||||||
|
// render the texture to the renderer
|
||||||
|
//
|
||||||
|
// Set the update_content_rect flag if the window or content size may have
|
||||||
|
// changed, so that the content rectangle is recomputed
|
||||||
|
void
|
||||||
|
screen_render(struct screen *screen, bool update_content_rect);
|
||||||
|
|
||||||
// switch the fullscreen mode
|
// switch the fullscreen mode
|
||||||
void
|
void
|
||||||
screen_switch_fullscreen(struct screen *screen);
|
screen_switch_fullscreen(struct screen *screen);
|
||||||
@ -115,13 +120,13 @@ screen_handle_event(struct screen *screen, SDL_Event *event);
|
|||||||
|
|
||||||
// convert point from window coordinates to frame coordinates
|
// convert point from window coordinates to frame coordinates
|
||||||
// x and y are expressed in pixels
|
// x and y are expressed in pixels
|
||||||
struct sc_point
|
struct point
|
||||||
screen_convert_window_to_frame_coords(struct screen *screen,
|
screen_convert_window_to_frame_coords(struct screen *screen,
|
||||||
int32_t x, int32_t y);
|
int32_t x, int32_t y);
|
||||||
|
|
||||||
// convert point from drawable coordinates to frame coordinates
|
// convert point from drawable coordinates to frame coordinates
|
||||||
// x and y are expressed in pixels
|
// x and y are expressed in pixels
|
||||||
struct sc_point
|
struct point
|
||||||
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
||||||
int32_t x, int32_t y);
|
int32_t x, int32_t y);
|
||||||
|
|
||||||
|
687
app/src/server.c
687
app/src/server.c
@ -8,16 +8,15 @@
|
|||||||
#include <SDL2/SDL_platform.h>
|
#include <SDL2/SDL_platform.h>
|
||||||
|
|
||||||
#include "adb.h"
|
#include "adb.h"
|
||||||
#include "util/file.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net_intr.h"
|
#include "util/net.h"
|
||||||
#include "util/process_intr.h"
|
#include "util/str_util.h"
|
||||||
#include "util/str.h"
|
|
||||||
|
|
||||||
#define SC_SERVER_FILENAME "scrcpy-server"
|
#define SOCKET_NAME "scrcpy"
|
||||||
|
#define SERVER_FILENAME "scrcpy-server"
|
||||||
|
|
||||||
#define SC_SERVER_PATH_DEFAULT PREFIX "/share/scrcpy/" SC_SERVER_FILENAME
|
#define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FILENAME
|
||||||
#define SC_DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
|
#define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
|
||||||
|
|
||||||
static char *
|
static char *
|
||||||
get_server_path(void) {
|
get_server_path(void) {
|
||||||
@ -29,7 +28,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 +41,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 = get_local_file_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);
|
||||||
@ -63,7 +62,7 @@ get_server_path(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_server_params_destroy(struct sc_server_params *params) {
|
server_params_destroy(struct server_params *params) {
|
||||||
// The server stores a copy of the params provided by the user
|
// The server stores a copy of the params provided by the user
|
||||||
free((char *) params->serial);
|
free((char *) params->serial);
|
||||||
free((char *) params->crop);
|
free((char *) params->crop);
|
||||||
@ -72,8 +71,7 @@ sc_server_params_destroy(struct sc_server_params *params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_server_params_copy(struct sc_server_params *dst,
|
server_params_copy(struct server_params *dst, const struct server_params *src) {
|
||||||
const struct sc_server_params *src) {
|
|
||||||
*dst = *src;
|
*dst = *src;
|
||||||
|
|
||||||
// The params reference user-allocated memory, so we must copy them to
|
// The params reference user-allocated memory, so we must copy them to
|
||||||
@ -97,24 +95,159 @@ sc_server_params_copy(struct sc_server_params *dst,
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
sc_server_params_destroy(dst);
|
server_params_destroy(dst);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
push_server(struct sc_intr *intr, const char *serial) {
|
push_server(const char *serial) {
|
||||||
char *server_path = get_server_path();
|
char *server_path = get_server_path();
|
||||||
if (!server_path) {
|
if (!server_path) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!sc_file_is_regular(server_path)) {
|
if (!is_regular_file(server_path)) {
|
||||||
LOGE("'%s' does not exist or is not a regular file\n", server_path);
|
LOGE("'%s' does not exist or is not a regular file\n", server_path);
|
||||||
free(server_path);
|
free(server_path);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
sc_pid pid = adb_push(serial, server_path, SC_DEVICE_SERVER_PATH);
|
process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH);
|
||||||
free(server_path);
|
free(server_path);
|
||||||
return sc_process_check_success_intr(intr, pid, "adb push");
|
return process_check_success(process, "adb push", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
enable_tunnel_reverse(const char *serial, uint16_t local_port) {
|
||||||
|
process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
|
||||||
|
return process_check_success(process, "adb reverse", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
disable_tunnel_reverse(const char *serial) {
|
||||||
|
process_t process = adb_reverse_remove(serial, SOCKET_NAME);
|
||||||
|
return process_check_success(process, "adb reverse --remove", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
enable_tunnel_forward(const char *serial, uint16_t local_port) {
|
||||||
|
process_t process = adb_forward(serial, local_port, SOCKET_NAME);
|
||||||
|
return process_check_success(process, "adb forward", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
disable_tunnel_forward(const char *serial, uint16_t local_port) {
|
||||||
|
process_t process = adb_forward_remove(serial, local_port);
|
||||||
|
return process_check_success(process, "adb forward --remove", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
disable_tunnel(struct server *server) {
|
||||||
|
const char *serial = server->params.serial;
|
||||||
|
if (server->tunnel_forward) {
|
||||||
|
return disable_tunnel_forward(serial, server->local_port);
|
||||||
|
}
|
||||||
|
return disable_tunnel_reverse(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) {
|
||||||
|
const char *serial = server->params.serial;
|
||||||
|
uint16_t port = port_range.first;
|
||||||
|
for (;;) {
|
||||||
|
if (!enable_tunnel_reverse(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(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;
|
||||||
|
|
||||||
|
const char *serial = server->params.serial;
|
||||||
|
uint16_t port = port_range.first;
|
||||||
|
for (;;) {
|
||||||
|
if (enable_tunnel_forward(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 *
|
||||||
@ -136,9 +269,8 @@ log_level_to_server_string(enum sc_log_level level) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static sc_pid
|
static process_t
|
||||||
execute_server(struct sc_server *server,
|
execute_server(struct server *server, const struct server_params *params) {
|
||||||
const struct sc_server_params *params) {
|
|
||||||
const char *serial = server->params.serial;
|
const char *serial = server->params.serial;
|
||||||
|
|
||||||
char max_size_string[6];
|
char max_size_string[6];
|
||||||
@ -154,7 +286,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 +308,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",
|
||||||
@ -201,384 +333,279 @@ execute_server(struct sc_server *server,
|
|||||||
return adb_execute(serial, cmd, ARRAY_LEN(cmd));
|
return adb_execute(serial, cmd, ARRAY_LEN(cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static sc_socket
|
||||||
connect_and_read_byte(struct sc_intr *intr, sc_socket socket, uint16_t port) {
|
connect_and_read_byte(uint16_t port) {
|
||||||
bool ok = net_connect_intr(intr, socket, IPV4_LOCALHOST, port);
|
sc_socket socket = net_connect(IPV4_LOCALHOST, port);
|
||||||
if (!ok) {
|
if (socket == SC_INVALID_SOCKET) {
|
||||||
return false;
|
return SC_INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
char byte;
|
char byte;
|
||||||
// the connection may succeed even if the server behind the "adb tunnel"
|
// the connection may succeed even if the server behind the "adb tunnel"
|
||||||
// is not listening, so read one byte to detect a working connection
|
// is not listening, so read one byte to detect a working connection
|
||||||
if (net_recv_intr(intr, socket, &byte, 1) != 1) {
|
if (net_recv(socket, &byte, 1) != 1) {
|
||||||
// the server is not listening yet behind the adb tunnel
|
// the server is not listening yet behind the adb tunnel
|
||||||
return false;
|
net_close(socket);
|
||||||
|
return 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) {
|
||||||
uint16_t port = server->tunnel.local_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, 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 server_params *params) {
|
||||||
const struct sc_server_callbacks *cbs, void *cbs_userdata) {
|
bool ok = server_params_copy(&server->params, params);
|
||||||
bool ok = sc_server_params_copy(&server->params, params);
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not copy server params");
|
LOGE("Could not copy server params");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server->process = PROCESS_NONE;
|
||||||
|
|
||||||
ok = sc_mutex_init(&server->mutex);
|
ok = sc_mutex_init(&server->mutex);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not create server mutex");
|
server_params_destroy(&server->params);
|
||||||
sc_server_params_destroy(&server->params);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_cond_init(&server->cond_stopped);
|
ok = sc_cond_init(&server->process_terminated_cond);
|
||||||
if (!ok) {
|
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);
|
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 attempts = 100;
|
|
||||||
sc_tick delay = SC_TICK_FROM_MS(100);
|
|
||||||
video_socket = connect_to_server(server, attempts, delay);
|
|
||||||
if (video_socket == SC_SOCKET_NONE) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
// we know that the device is listening, we don't need several attempts
|
|
||||||
control_socket = net_socket();
|
|
||||||
if (control_socket == SC_SOCKET_NONE) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
bool ok = net_connect_intr(&server->intr, control_socket,
|
|
||||||
IPV4_LOCALHOST, tunnel->local_port);
|
|
||||||
if (!ok) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// we don't need the adb tunnel anymore
|
|
||||||
sc_adb_tunnel_close(tunnel, &server->intr, serial);
|
|
||||||
|
|
||||||
// The sockets will be closed on stop if device_read_info() fails
|
|
||||||
bool ok = device_read_info(&server->intr, video_socket, info);
|
|
||||||
if (!ok) {
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(video_socket != SC_SOCKET_NONE);
|
|
||||||
assert(control_socket != SC_SOCKET_NONE);
|
|
||||||
|
|
||||||
server->video_socket = video_socket;
|
|
||||||
server->control_socket = control_socket;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
fail:
|
|
||||||
if (video_socket != SC_SOCKET_NONE) {
|
|
||||||
if (!net_close(video_socket)) {
|
|
||||||
LOGW("Could not close video socket");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (control_socket != SC_SOCKET_NONE) {
|
|
||||||
if (!net_close(control_socket)) {
|
|
||||||
LOGW("Could not close control socket");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always leave this function with tunnel disabled
|
|
||||||
sc_adb_tunnel_close(tunnel, &server->intr, serial);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
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 char *
|
|
||||||
sc_server_get_serialno(struct sc_intr *intr) {
|
|
||||||
sc_pipe pout;
|
|
||||||
sc_pid pid = adb_get_serialno(&pout);
|
|
||||||
if (pid == SC_PROCESS_NONE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
char buf[128];
|
|
||||||
ssize_t r = sc_pipe_read_all_intr(intr, pid, pout, buf, sizeof(buf));
|
|
||||||
sc_pipe_close(pout);
|
|
||||||
|
|
||||||
bool ok = sc_process_check_success_intr(intr, pid, "adb get-serialno");
|
|
||||||
sc_process_close(pid);
|
|
||||||
if (!ok) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_str_truncate(buf, r, " \r\n");
|
|
||||||
|
|
||||||
return strdup(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = sc_server_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;
|
||||||
|
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 = &server->params;
|
||||||
|
|
||||||
bool ok = push_server(&server->intr, params->serial);
|
if (!push_server(params->serial)) {
|
||||||
if (!ok) {
|
/* server->serial will be freed on server_destroy() */
|
||||||
goto error_connection_failed;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_adb_tunnel_open(&server->tunnel, &server->intr, params->serial,
|
if (!enable_tunnel_any_port(server, params->port_range,
|
||||||
params->port_range, params->force_adb_forward);
|
params->force_adb_forward)) {
|
||||||
if (!ok) {
|
return false;
|
||||||
goto error_connection_failed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// server will connect to our server socket
|
// server will connect to our server socket
|
||||||
sc_pid pid = execute_server(server, params);
|
server->process = execute_server(server, params);
|
||||||
if (pid == SC_PROCESS_NONE) {
|
if (server->process == PROCESS_NONE) {
|
||||||
sc_adb_tunnel_close(&server->tunnel, &server->intr, params->serial);
|
goto error;
|
||||||
goto error_connection_failed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct sc_process_listener listener = {
|
// If the server process dies before connecting to the server socket, then
|
||||||
.on_terminated = sc_server_on_terminated,
|
// the client will be stuck forever on accept(). To avoid the problem, we
|
||||||
};
|
// must be able to wake up the accept() call when the server dies. To keep
|
||||||
struct sc_process_observer observer;
|
// things simple and multiplatform, just spawn a new thread waiting for the
|
||||||
ok = sc_process_observer_init(&observer, pid, &listener, server);
|
// server process and calling shutdown()/close() on the server socket if
|
||||||
|
// necessary to wake up any accept() blocking call.
|
||||||
|
bool ok = sc_thread_create(&server->wait_server_thread, run_wait_server,
|
||||||
|
"wait-server", server);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
sc_process_terminate(pid);
|
process_terminate(server->process);
|
||||||
sc_process_wait(pid, true); // ignore exit code
|
process_wait(server->process, true); // ignore exit code
|
||||||
sc_adb_tunnel_close(&server->tunnel, &server->intr, params->serial);
|
goto error;
|
||||||
goto error_connection_failed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_server_connect_to(server, &server->info);
|
server->tunnel_enabled = true;
|
||||||
// The tunnel is always closed by server_connect_to()
|
|
||||||
if (!ok) {
|
return true;
|
||||||
sc_process_terminate(pid);
|
|
||||||
sc_process_wait(pid, true); // ignore exit code
|
error:
|
||||||
sc_process_observer_join(&observer);
|
// 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, char *device_name,
|
||||||
|
struct size *size) {
|
||||||
|
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';
|
||||||
|
// strcpy is safe here, since name contains at least
|
||||||
|
// DEVICE_NAME_FIELD_LENGTH bytes and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
|
||||||
|
strcpy(device_name, (char *) buf);
|
||||||
|
size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8)
|
||||||
|
| buf[DEVICE_NAME_FIELD_LENGTH + 1];
|
||||||
|
size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8)
|
||||||
|
| buf[DEVICE_NAME_FIELD_LENGTH + 3];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
server_connect_to(struct server *server, char *device_name, struct size *size) {
|
||||||
|
if (!server->tunnel_forward) {
|
||||||
|
server->video_socket = net_accept(server->server_socket);
|
||||||
|
if (server->video_socket == 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, device_name, size);
|
||||||
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 != PROCESS_NONE);
|
||||||
|
|
||||||
|
if (server->tunnel_enabled) {
|
||||||
|
// ignore failure
|
||||||
|
disable_tunnel(server);
|
||||||
}
|
}
|
||||||
sc_mutex_unlock(&server->mutex);
|
|
||||||
|
|
||||||
// Give some delay for the server to terminate properly
|
// Give some delay for the server to terminate properly
|
||||||
|
sc_mutex_lock(&server->mutex);
|
||||||
|
bool signaled = false;
|
||||||
|
if (!server->process_terminated) {
|
||||||
#define WATCHDOG_DELAY SC_TICK_FROM_SEC(1)
|
#define WATCHDOG_DELAY SC_TICK_FROM_SEC(1)
|
||||||
sc_tick deadline = sc_tick_now() + WATCHDOG_DELAY;
|
signaled = sc_cond_timedwait(&server->process_terminated_cond,
|
||||||
bool terminated = sc_process_observer_timedwait(&observer, deadline);
|
&server->mutex,
|
||||||
|
sc_tick_now() + WATCHDOG_DELAY);
|
||||||
|
}
|
||||||
|
sc_mutex_unlock(&server->mutex);
|
||||||
|
|
||||||
// After this delay, kill the server if it's not dead already.
|
// After this delay, kill the server if it's not dead already.
|
||||||
// On some devices, closing the sockets is not sufficient to wake up the
|
// On some devices, closing the sockets is not sufficient to wake up the
|
||||||
// blocking calls while the device is asleep.
|
// blocking calls while the device is asleep.
|
||||||
if (!terminated) {
|
if (!signaled) {
|
||||||
// The process may have terminated since the check, but it is not
|
// The process is terminated, but not reaped (closed) yet, so its PID
|
||||||
// reaped (closed) yet, so its PID is still valid, and it is ok to call
|
// is still valid.
|
||||||
// sc_process_terminate() even in that case.
|
|
||||||
LOGW("Killing the server...");
|
LOGW("Killing the server...");
|
||||||
sc_process_terminate(pid);
|
process_terminate(server->process);
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_process_observer_join(&observer);
|
sc_thread_join(&server->wait_server_thread, NULL);
|
||||||
sc_process_observer_destroy(&observer);
|
process_close(server->process);
|
||||||
|
|
||||||
sc_process_close(pid);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
error_connection_failed:
|
|
||||||
server->cbs->on_connection_failed(server, server->cbs_userdata);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_server_start(struct sc_server *server) {
|
|
||||||
bool ok = sc_thread_create(&server->thread, run_server, "server", server);
|
|
||||||
if (!ok) {
|
|
||||||
LOGE("Could not create server thread");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_server_stop(struct sc_server *server) {
|
server_destroy(struct server *server) {
|
||||||
sc_mutex_lock(&server->mutex);
|
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) {
|
||||||
|
if (!net_close(server->video_socket)) {
|
||||||
|
LOGW("Could not close video socket");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (server->control_socket != SC_INVALID_SOCKET) {
|
||||||
|
if (!net_close(server->control_socket)) {
|
||||||
|
LOGW("Could not close control socket");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sc_thread_join(&server->thread, NULL);
|
server_params_destroy(&server->params);
|
||||||
}
|
sc_cond_destroy(&server->process_terminated_cond);
|
||||||
|
|
||||||
void
|
|
||||||
sc_server_destroy(struct sc_server *server) {
|
|
||||||
sc_server_params_destroy(&server->params);
|
|
||||||
sc_intr_destroy(&server->intr);
|
|
||||||
sc_cond_destroy(&server->cond_stopped);
|
|
||||||
sc_mutex_destroy(&server->mutex);
|
sc_mutex_destroy(&server->mutex);
|
||||||
}
|
}
|
||||||
|
@ -8,21 +8,13 @@
|
|||||||
#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
|
struct server_params {
|
||||||
struct sc_server_info {
|
|
||||||
char device_name[SC_DEVICE_NAME_FIELD_LENGTH];
|
|
||||||
struct sc_size frame_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sc_server_params {
|
|
||||||
const char *serial;
|
const char *serial;
|
||||||
enum sc_log_level log_level;
|
enum sc_log_level log_level;
|
||||||
const char *crop;
|
const char *crop;
|
||||||
@ -41,62 +33,45 @@ struct sc_server_params {
|
|||||||
bool power_off_on_close;
|
bool power_off_on_close;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_server {
|
struct server {
|
||||||
// The internal allocated strings are copies owned by the server
|
// The internal allocated strings are copies owned by the server
|
||||||
struct sc_server_params params;
|
struct server_params params;
|
||||||
|
|
||||||
sc_thread thread;
|
process_t process;
|
||||||
struct sc_server_info info; // initialized once connected
|
sc_thread wait_server_thread;
|
||||||
|
|
||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
sc_cond cond_stopped;
|
sc_cond process_terminated_cond;
|
||||||
bool stopped;
|
bool process_terminated;
|
||||||
|
|
||||||
struct sc_intr intr;
|
|
||||||
struct sc_adb_tunnel tunnel;
|
|
||||||
|
|
||||||
|
sc_socket server_socket; // only used if !tunnel_forward
|
||||||
sc_socket video_socket;
|
sc_socket video_socket;
|
||||||
sc_socket control_socket;
|
sc_socket control_socket;
|
||||||
|
uint16_t local_port; // selected from port_range
|
||||||
const struct sc_server_callbacks *cbs;
|
bool tunnel_enabled;
|
||||||
void *cbs_userdata;
|
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
|
||||||
};
|
|
||||||
|
|
||||||
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
|
// 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 server_params *params);
|
||||||
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);
|
||||||
|
|
||||||
|
#define DEVICE_NAME_FIELD_LENGTH 64
|
||||||
|
// block until the communication with the server is established
|
||||||
|
// device_name must point to a buffer of at least DEVICE_NAME_FIELD_LENGTH bytes
|
||||||
|
bool
|
||||||
|
server_connect_to(struct server *server, char *device_name, struct size *size);
|
||||||
|
|
||||||
// disconnect and kill the server process
|
// disconnect and kill the server process
|
||||||
void
|
void
|
||||||
sc_server_stop(struct sc_server *server);
|
server_stop(struct server *server);
|
||||||
|
|
||||||
// close and release sockets
|
// close and release sockets
|
||||||
void
|
void
|
||||||
sc_server_destroy(struct sc_server *server);
|
server_destroy(struct server *server);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,75 +0,0 @@
|
|||||||
#include "util/file.h"
|
|
||||||
|
|
||||||
#include <limits.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_file_executable_exists(const char *file) {
|
|
||||||
char *path = getenv("PATH");
|
|
||||||
if (!path)
|
|
||||||
return false;
|
|
||||||
path = strdup(path);
|
|
||||||
if (!path)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
bool ret = false;
|
|
||||||
size_t file_len = strlen(file);
|
|
||||||
char *saveptr;
|
|
||||||
for (char *dir = strtok_r(path, ":", &saveptr); dir;
|
|
||||||
dir = strtok_r(NULL, ":", &saveptr)) {
|
|
||||||
size_t dir_len = strlen(dir);
|
|
||||||
char *fullpath = malloc(dir_len + file_len + 2);
|
|
||||||
if (!fullpath)
|
|
||||||
continue;
|
|
||||||
memcpy(fullpath, dir, dir_len);
|
|
||||||
fullpath[dir_len] = '/';
|
|
||||||
memcpy(fullpath + dir_len + 1, file, file_len + 1);
|
|
||||||
|
|
||||||
struct stat sb;
|
|
||||||
bool fullpath_executable = stat(fullpath, &sb) == 0 &&
|
|
||||||
sb.st_mode & S_IXUSR;
|
|
||||||
free(fullpath);
|
|
||||||
if (fullpath_executable) {
|
|
||||||
ret = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free(path);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *
|
|
||||||
sc_file_get_executable_path(void) {
|
|
||||||
// <https://stackoverflow.com/a/1024937/1987178>
|
|
||||||
#ifdef __linux__
|
|
||||||
char buf[PATH_MAX + 1]; // +1 for the null byte
|
|
||||||
ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX);
|
|
||||||
if (len == -1) {
|
|
||||||
perror("readlink");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
buf[len] = '\0';
|
|
||||||
return strdup(buf);
|
|
||||||
#else
|
|
||||||
// in practice, we only need this feature for portable builds, only used on
|
|
||||||
// Windows, so we don't care implementing it for every platform
|
|
||||||
// (it's useful to have a working version on Linux for debugging though)
|
|
||||||
return NULL;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_file_is_regular(const char *path) {
|
|
||||||
struct stat path_stat;
|
|
||||||
|
|
||||||
if (stat(path, &path_stat)) {
|
|
||||||
perror("stat");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return S_ISREG(path_stat.st_mode);
|
|
||||||
}
|
|
||||||
|
|
@ -3,16 +3,56 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <limits.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
enum sc_process_result
|
bool
|
||||||
sc_process_execute_p(const char *const argv[], sc_pid *pid,
|
search_executable(const char *file) {
|
||||||
int *pin, int *pout, int *perr) {
|
char *path = getenv("PATH");
|
||||||
|
if (!path)
|
||||||
|
return false;
|
||||||
|
path = strdup(path);
|
||||||
|
if (!path)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool ret = false;
|
||||||
|
size_t file_len = strlen(file);
|
||||||
|
char *saveptr;
|
||||||
|
for (char *dir = strtok_r(path, ":", &saveptr); dir;
|
||||||
|
dir = strtok_r(NULL, ":", &saveptr)) {
|
||||||
|
size_t dir_len = strlen(dir);
|
||||||
|
char *fullpath = malloc(dir_len + file_len + 2);
|
||||||
|
if (!fullpath)
|
||||||
|
continue;
|
||||||
|
memcpy(fullpath, dir, dir_len);
|
||||||
|
fullpath[dir_len] = '/';
|
||||||
|
memcpy(fullpath + dir_len + 1, file, file_len + 1);
|
||||||
|
|
||||||
|
struct stat sb;
|
||||||
|
bool fullpath_executable = stat(fullpath, &sb) == 0 &&
|
||||||
|
sb.st_mode & S_IXUSR;
|
||||||
|
free(fullpath);
|
||||||
|
if (fullpath_executable) {
|
||||||
|
ret = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(path);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum process_result
|
||||||
|
process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
|
||||||
|
int *pipe_stdout, int *pipe_stderr) {
|
||||||
int in[2];
|
int in[2];
|
||||||
int out[2];
|
int out[2];
|
||||||
int err[2];
|
int err[2];
|
||||||
@ -20,44 +60,44 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid,
|
|||||||
|
|
||||||
if (pipe(internal) == -1) {
|
if (pipe(internal) == -1) {
|
||||||
perror("pipe");
|
perror("pipe");
|
||||||
return SC_PROCESS_ERROR_GENERIC;
|
return PROCESS_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
if (pin) {
|
if (pipe_stdin) {
|
||||||
if (pipe(in) == -1) {
|
if (pipe(in) == -1) {
|
||||||
perror("pipe");
|
perror("pipe");
|
||||||
close(internal[0]);
|
close(internal[0]);
|
||||||
close(internal[1]);
|
close(internal[1]);
|
||||||
return SC_PROCESS_ERROR_GENERIC;
|
return PROCESS_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pout) {
|
if (pipe_stdout) {
|
||||||
if (pipe(out) == -1) {
|
if (pipe(out) == -1) {
|
||||||
perror("pipe");
|
perror("pipe");
|
||||||
// clean up
|
// clean up
|
||||||
if (pin) {
|
if (pipe_stdin) {
|
||||||
close(in[0]);
|
close(in[0]);
|
||||||
close(in[1]);
|
close(in[1]);
|
||||||
}
|
}
|
||||||
close(internal[0]);
|
close(internal[0]);
|
||||||
close(internal[1]);
|
close(internal[1]);
|
||||||
return SC_PROCESS_ERROR_GENERIC;
|
return PROCESS_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (perr) {
|
if (pipe_stderr) {
|
||||||
if (pipe(err) == -1) {
|
if (pipe(err) == -1) {
|
||||||
perror("pipe");
|
perror("pipe");
|
||||||
// clean up
|
// clean up
|
||||||
if (pout) {
|
if (pipe_stdout) {
|
||||||
close(out[0]);
|
close(out[0]);
|
||||||
close(out[1]);
|
close(out[1]);
|
||||||
}
|
}
|
||||||
if (pin) {
|
if (pipe_stdin) {
|
||||||
close(in[0]);
|
close(in[0]);
|
||||||
close(in[1]);
|
close(in[1]);
|
||||||
}
|
}
|
||||||
close(internal[0]);
|
close(internal[0]);
|
||||||
close(internal[1]);
|
close(internal[1]);
|
||||||
return SC_PROCESS_ERROR_GENERIC;
|
return PROCESS_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,39 +105,39 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid,
|
|||||||
if (*pid == -1) {
|
if (*pid == -1) {
|
||||||
perror("fork");
|
perror("fork");
|
||||||
// clean up
|
// clean up
|
||||||
if (perr) {
|
if (pipe_stderr) {
|
||||||
close(err[0]);
|
close(err[0]);
|
||||||
close(err[1]);
|
close(err[1]);
|
||||||
}
|
}
|
||||||
if (pout) {
|
if (pipe_stdout) {
|
||||||
close(out[0]);
|
close(out[0]);
|
||||||
close(out[1]);
|
close(out[1]);
|
||||||
}
|
}
|
||||||
if (pin) {
|
if (pipe_stdin) {
|
||||||
close(in[0]);
|
close(in[0]);
|
||||||
close(in[1]);
|
close(in[1]);
|
||||||
}
|
}
|
||||||
close(internal[0]);
|
close(internal[0]);
|
||||||
close(internal[1]);
|
close(internal[1]);
|
||||||
return SC_PROCESS_ERROR_GENERIC;
|
return PROCESS_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*pid == 0) {
|
if (*pid == 0) {
|
||||||
if (pin) {
|
if (pipe_stdin) {
|
||||||
if (in[0] != STDIN_FILENO) {
|
if (in[0] != STDIN_FILENO) {
|
||||||
dup2(in[0], STDIN_FILENO);
|
dup2(in[0], STDIN_FILENO);
|
||||||
close(in[0]);
|
close(in[0]);
|
||||||
}
|
}
|
||||||
close(in[1]);
|
close(in[1]);
|
||||||
}
|
}
|
||||||
if (pout) {
|
if (pipe_stdout) {
|
||||||
if (out[1] != STDOUT_FILENO) {
|
if (out[1] != STDOUT_FILENO) {
|
||||||
dup2(out[1], STDOUT_FILENO);
|
dup2(out[1], STDOUT_FILENO);
|
||||||
close(out[1]);
|
close(out[1]);
|
||||||
}
|
}
|
||||||
close(out[0]);
|
close(out[0]);
|
||||||
}
|
}
|
||||||
if (perr) {
|
if (pipe_stderr) {
|
||||||
if (err[1] != STDERR_FILENO) {
|
if (err[1] != STDERR_FILENO) {
|
||||||
dup2(err[1], STDERR_FILENO);
|
dup2(err[1], STDERR_FILENO);
|
||||||
close(err[1]);
|
close(err[1]);
|
||||||
@ -105,15 +145,15 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid,
|
|||||||
close(err[0]);
|
close(err[0]);
|
||||||
}
|
}
|
||||||
close(internal[0]);
|
close(internal[0]);
|
||||||
enum sc_process_result err;
|
enum process_result err;
|
||||||
if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) {
|
if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) {
|
||||||
execvp(argv[0], (char *const *) argv);
|
execvp(argv[0], (char *const *) argv);
|
||||||
perror("exec");
|
perror("exec");
|
||||||
err = errno == ENOENT ? SC_PROCESS_ERROR_MISSING_BINARY
|
err = errno == ENOENT ? PROCESS_ERROR_MISSING_BINARY
|
||||||
: SC_PROCESS_ERROR_GENERIC;
|
: PROCESS_ERROR_GENERIC;
|
||||||
} else {
|
} else {
|
||||||
perror("fcntl");
|
perror("fcntl");
|
||||||
err = SC_PROCESS_ERROR_GENERIC;
|
err = PROCESS_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
// send err to the parent
|
// send err to the parent
|
||||||
if (write(internal[1], &err, sizeof(err)) == -1) {
|
if (write(internal[1], &err, sizeof(err)) == -1) {
|
||||||
@ -128,33 +168,38 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid,
|
|||||||
|
|
||||||
close(internal[1]);
|
close(internal[1]);
|
||||||
|
|
||||||
enum sc_process_result res = SC_PROCESS_SUCCESS;
|
enum process_result res = PROCESS_SUCCESS;
|
||||||
// wait for EOF or receive err from child
|
// wait for EOF or receive err from child
|
||||||
if (read(internal[0], &res, sizeof(res)) == -1) {
|
if (read(internal[0], &res, sizeof(res)) == -1) {
|
||||||
perror("read");
|
perror("read");
|
||||||
res = SC_PROCESS_ERROR_GENERIC;
|
res = PROCESS_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
close(internal[0]);
|
close(internal[0]);
|
||||||
|
|
||||||
if (pin) {
|
if (pipe_stdin) {
|
||||||
close(in[0]);
|
close(in[0]);
|
||||||
*pin = in[1];
|
*pipe_stdin = in[1];
|
||||||
}
|
}
|
||||||
if (pout) {
|
if (pipe_stdout) {
|
||||||
*pout = out[0];
|
*pipe_stdout = out[0];
|
||||||
close(out[1]);
|
close(out[1]);
|
||||||
}
|
}
|
||||||
if (perr) {
|
if (pipe_stderr) {
|
||||||
*perr = err[0];
|
*pipe_stderr = err[0];
|
||||||
close(err[1]);
|
close(err[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum process_result
|
||||||
|
process_execute(const char *const argv[], pid_t *pid) {
|
||||||
|
return process_execute_redirect(argv, pid, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_process_terminate(pid_t pid) {
|
process_terminate(pid_t pid) {
|
||||||
if (pid <= 0) {
|
if (pid <= 0) {
|
||||||
LOGC("Requested to kill %d, this is an error. Please report the bug.\n",
|
LOGC("Requested to kill %d, this is an error. Please report the bug.\n",
|
||||||
(int) pid);
|
(int) pid);
|
||||||
@ -163,8 +208,8 @@ sc_process_terminate(pid_t pid) {
|
|||||||
return kill(pid, SIGKILL) != -1;
|
return kill(pid, SIGKILL) != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_exit_code
|
exit_code_t
|
||||||
sc_process_wait(pid_t pid, bool close) {
|
process_wait(pid_t pid, bool close) {
|
||||||
int code;
|
int code;
|
||||||
int options = WEXITED;
|
int options = WEXITED;
|
||||||
if (!close) {
|
if (!close) {
|
||||||
@ -175,7 +220,7 @@ sc_process_wait(pid_t pid, bool close) {
|
|||||||
int r = waitid(P_PID, pid, &info, options);
|
int r = waitid(P_PID, pid, &info, options);
|
||||||
if (r == -1 || info.si_code != CLD_EXITED) {
|
if (r == -1 || info.si_code != CLD_EXITED) {
|
||||||
// could not wait, or exited unexpectedly, probably by a signal
|
// could not wait, or exited unexpectedly, probably by a signal
|
||||||
code = SC_EXIT_CODE_NONE;
|
code = NO_EXIT_CODE;
|
||||||
} else {
|
} else {
|
||||||
code = info.si_status;
|
code = info.si_status;
|
||||||
}
|
}
|
||||||
@ -183,17 +228,48 @@ sc_process_wait(pid_t pid, bool close) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_process_close(pid_t pid) {
|
process_close(pid_t pid) {
|
||||||
sc_process_wait(pid, true); // ignore exit code
|
process_wait(pid, true); // ignore exit code
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
get_executable_path(void) {
|
||||||
|
// <https://stackoverflow.com/a/1024937/1987178>
|
||||||
|
#ifdef __linux__
|
||||||
|
char buf[PATH_MAX + 1]; // +1 for the null byte
|
||||||
|
ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX);
|
||||||
|
if (len == -1) {
|
||||||
|
perror("readlink");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
buf[len] = '\0';
|
||||||
|
return strdup(buf);
|
||||||
|
#else
|
||||||
|
// in practice, we only need this feature for portable builds, only used on
|
||||||
|
// Windows, so we don't care implementing it for every platform
|
||||||
|
// (it's useful to have a working version on Linux for debugging though)
|
||||||
|
return NULL;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
is_regular_file(const char *path) {
|
||||||
|
struct stat path_stat;
|
||||||
|
|
||||||
|
if (stat(path, &path_stat)) {
|
||||||
|
perror("stat");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return S_ISREG(path_stat.st_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t
|
ssize_t
|
||||||
sc_pipe_read(int pipe, char *data, size_t len) {
|
read_pipe(int pipe, char *data, size_t len) {
|
||||||
return read(pipe, data, len);
|
return read(pipe, data, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_pipe_close(int pipe) {
|
close_pipe(int pipe) {
|
||||||
if (close(pipe)) {
|
if (close(pipe)) {
|
||||||
perror("close pipe");
|
perror("close pipe");
|
||||||
}
|
}
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
#include "util/file.h"
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#include <sys/stat.h>
|
|
||||||
|
|
||||||
#include "util/log.h"
|
|
||||||
#include "util/str.h"
|
|
||||||
|
|
||||||
char *
|
|
||||||
sc_file_get_executable_path(void) {
|
|
||||||
HMODULE hModule = GetModuleHandleW(NULL);
|
|
||||||
if (!hModule) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
WCHAR buf[MAX_PATH + 1]; // +1 for the null byte
|
|
||||||
int len = GetModuleFileNameW(hModule, buf, MAX_PATH);
|
|
||||||
if (!len) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
buf[len] = '\0';
|
|
||||||
return sc_str_from_wchars(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_file_is_regular(const char *path) {
|
|
||||||
wchar_t *wide_path = sc_str_to_wchars(path);
|
|
||||||
if (!wide_path) {
|
|
||||||
LOGC("Could not allocate wide char string");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct _stat path_stat;
|
|
||||||
int r = _wstat(wide_path, &path_stat);
|
|
||||||
free(wide_path);
|
|
||||||
|
|
||||||
if (r) {
|
|
||||||
perror("stat");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return S_ISREG(path_stat.st_mode);
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +1,10 @@
|
|||||||
// <https://devblogs.microsoft.com/oldnewthing/20111216-00/?p=8873>
|
|
||||||
#define _WIN32_WINNT 0x0600 // For extended process API
|
|
||||||
|
|
||||||
#include "util/process.h"
|
#include "util/process.h"
|
||||||
|
|
||||||
#include <processthreadsapi.h>
|
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
#define CMD_MAX_LEN 8192
|
#define CMD_MAX_LEN 8192
|
||||||
|
|
||||||
@ -18,21 +14,19 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) {
|
|||||||
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
|
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
|
||||||
// only make it work for this very specific program
|
// only make it work for this very specific program
|
||||||
// (don't handle escaping nor quotes)
|
// (don't handle escaping nor quotes)
|
||||||
size_t ret = sc_str_join(cmd, argv, ' ', len);
|
size_t ret = xstrjoin(cmd, argv, ' ', len);
|
||||||
if (ret >= len) {
|
if (ret >= len) {
|
||||||
LOGE("Command too long (%" SC_PRIsizet " chars)", len - 1);
|
LOGE("Command too long (%" PRIsizet " chars)", len - 1);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum sc_process_result
|
enum process_result
|
||||||
sc_process_execute_p(const char *const argv[], HANDLE *handle,
|
process_execute_redirect(const char *const argv[], HANDLE *handle,
|
||||||
HANDLE *pin, HANDLE *pout, HANDLE *perr) {
|
HANDLE *pipe_stdin, HANDLE *pipe_stdout,
|
||||||
enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC;
|
HANDLE *pipe_stderr) {
|
||||||
|
enum process_result ret = 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);
|
||||||
@ -42,178 +36,130 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle,
|
|||||||
HANDLE stdin_read_handle;
|
HANDLE stdin_read_handle;
|
||||||
HANDLE stdout_write_handle;
|
HANDLE stdout_write_handle;
|
||||||
HANDLE stderr_write_handle;
|
HANDLE stderr_write_handle;
|
||||||
if (pin) {
|
if (pipe_stdin) {
|
||||||
if (!CreatePipe(&stdin_read_handle, pin, &sa, 0)) {
|
if (!CreatePipe(&stdin_read_handle, pipe_stdin, &sa, 0)) {
|
||||||
perror("pipe");
|
perror("pipe");
|
||||||
return SC_PROCESS_ERROR_GENERIC;
|
return PROCESS_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
if (!SetHandleInformation(*pin, HANDLE_FLAG_INHERIT, 0)) {
|
if (!SetHandleInformation(*pipe_stdin, HANDLE_FLAG_INHERIT, 0)) {
|
||||||
LOGE("SetHandleInformation stdin failed");
|
LOGE("SetHandleInformation stdin failed");
|
||||||
goto error_close_stdin;
|
goto error_close_stdin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pout) {
|
if (pipe_stdout) {
|
||||||
if (!CreatePipe(pout, &stdout_write_handle, &sa, 0)) {
|
if (!CreatePipe(pipe_stdout, &stdout_write_handle, &sa, 0)) {
|
||||||
perror("pipe");
|
perror("pipe");
|
||||||
goto error_close_stdin;
|
goto error_close_stdin;
|
||||||
}
|
}
|
||||||
if (!SetHandleInformation(*pout, HANDLE_FLAG_INHERIT, 0)) {
|
if (!SetHandleInformation(*pipe_stdout, HANDLE_FLAG_INHERIT, 0)) {
|
||||||
LOGE("SetHandleInformation stdout failed");
|
LOGE("SetHandleInformation stdout failed");
|
||||||
goto error_close_stdout;
|
goto error_close_stdout;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (perr) {
|
if (pipe_stderr) {
|
||||||
if (!CreatePipe(perr, &stderr_write_handle, &sa, 0)) {
|
if (!CreatePipe(pipe_stderr, &stderr_write_handle, &sa, 0)) {
|
||||||
perror("pipe");
|
perror("pipe");
|
||||||
goto error_close_stdout;
|
goto error_close_stdout;
|
||||||
}
|
}
|
||||||
if (!SetHandleInformation(*perr, HANDLE_FLAG_INHERIT, 0)) {
|
if (!SetHandleInformation(*pipe_stderr, HANDLE_FLAG_INHERIT, 0)) {
|
||||||
LOGE("SetHandleInformation stderr failed");
|
LOGE("SetHandleInformation stderr failed");
|
||||||
goto error_close_stderr;
|
goto error_close_stderr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
STARTUPINFOEXW si;
|
STARTUPINFOW si;
|
||||||
PROCESS_INFORMATION pi;
|
PROCESS_INFORMATION pi;
|
||||||
memset(&si, 0, sizeof(si));
|
memset(&si, 0, sizeof(si));
|
||||||
si.StartupInfo.cb = sizeof(si);
|
si.cb = sizeof(si);
|
||||||
HANDLE handles[3];
|
if (pipe_stdin || pipe_stdout || pipe_stderr) {
|
||||||
|
si.dwFlags = STARTF_USESTDHANDLES;
|
||||||
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL;
|
if (pipe_stdin) {
|
||||||
if (handle_count) {
|
si.hStdInput = stdin_read_handle;
|
||||||
si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
|
|
||||||
if (pin) {
|
|
||||||
si.StartupInfo.hStdInput = stdin_read_handle;
|
|
||||||
}
|
}
|
||||||
if (pout) {
|
if (pipe_stdout) {
|
||||||
si.StartupInfo.hStdOutput = stdout_write_handle;
|
si.hStdOutput = stdout_write_handle;
|
||||||
}
|
}
|
||||||
if (perr) {
|
if (pipe_stderr) {
|
||||||
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 = 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
|
||||||
if (pin) {
|
if (pipe_stdin) {
|
||||||
CloseHandle(stdin_read_handle);
|
CloseHandle(stdin_read_handle);
|
||||||
}
|
}
|
||||||
if (pout) {
|
if (pipe_stdout) {
|
||||||
CloseHandle(stdout_write_handle);
|
CloseHandle(stdout_write_handle);
|
||||||
}
|
}
|
||||||
if (perr) {
|
if (pipe_stderr) {
|
||||||
CloseHandle(stderr_write_handle);
|
CloseHandle(stderr_write_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(wide);
|
||||||
*handle = pi.hProcess;
|
*handle = pi.hProcess;
|
||||||
|
|
||||||
return SC_PROCESS_SUCCESS;
|
return PROCESS_SUCCESS;
|
||||||
|
|
||||||
error_free_attribute_list:
|
|
||||||
if (lpAttributeList) {
|
|
||||||
DeleteProcThreadAttributeList(lpAttributeList);
|
|
||||||
free(lpAttributeList);
|
|
||||||
}
|
|
||||||
error_close_stderr:
|
error_close_stderr:
|
||||||
if (perr) {
|
if (pipe_stderr) {
|
||||||
CloseHandle(*perr);
|
CloseHandle(*pipe_stderr);
|
||||||
CloseHandle(stderr_write_handle);
|
CloseHandle(stderr_write_handle);
|
||||||
}
|
}
|
||||||
error_close_stdout:
|
error_close_stdout:
|
||||||
if (pout) {
|
if (pipe_stdout) {
|
||||||
CloseHandle(*pout);
|
CloseHandle(*pipe_stdout);
|
||||||
CloseHandle(stdout_write_handle);
|
CloseHandle(stdout_write_handle);
|
||||||
}
|
}
|
||||||
error_close_stdin:
|
error_close_stdin:
|
||||||
if (pin) {
|
if (pipe_stdin) {
|
||||||
CloseHandle(*pin);
|
CloseHandle(*pipe_stdin);
|
||||||
CloseHandle(stdin_read_handle);
|
CloseHandle(stdin_read_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum process_result
|
||||||
|
process_execute(const char *const argv[], HANDLE *handle) {
|
||||||
|
return process_execute_redirect(argv, handle, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_process_terminate(HANDLE handle) {
|
process_terminate(HANDLE handle) {
|
||||||
return TerminateProcess(handle, 1);
|
return TerminateProcess(handle, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_exit_code
|
exit_code_t
|
||||||
sc_process_wait(HANDLE handle, bool close) {
|
process_wait(HANDLE handle, bool close) {
|
||||||
DWORD code;
|
DWORD code;
|
||||||
if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0
|
if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0
|
||||||
|| !GetExitCodeProcess(handle, &code)) {
|
|| !GetExitCodeProcess(handle, &code)) {
|
||||||
// could not wait or retrieve the exit code
|
// could not wait or retrieve the exit code
|
||||||
code = SC_EXIT_CODE_NONE;
|
code = NO_EXIT_CODE; // max value, it's unsigned
|
||||||
}
|
}
|
||||||
if (close) {
|
if (close) {
|
||||||
CloseHandle(handle);
|
CloseHandle(handle);
|
||||||
@ -222,14 +168,48 @@ sc_process_wait(HANDLE handle, bool close) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_process_close(HANDLE handle) {
|
process_close(HANDLE handle) {
|
||||||
bool closed = CloseHandle(handle);
|
bool closed = CloseHandle(handle);
|
||||||
assert(closed);
|
assert(closed);
|
||||||
(void) closed;
|
(void) closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
get_executable_path(void) {
|
||||||
|
HMODULE hModule = GetModuleHandleW(NULL);
|
||||||
|
if (!hModule) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
WCHAR buf[MAX_PATH + 1]; // +1 for the null byte
|
||||||
|
int len = GetModuleFileNameW(hModule, buf, MAX_PATH);
|
||||||
|
if (!len) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
buf[len] = '\0';
|
||||||
|
return utf8_from_wide_char(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
is_regular_file(const char *path) {
|
||||||
|
wchar_t *wide_path = utf8_to_wide_char(path);
|
||||||
|
if (!wide_path) {
|
||||||
|
LOGC("Could not allocate wide char string");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct _stat path_stat;
|
||||||
|
int r = _wstat(wide_path, &path_stat);
|
||||||
|
free(wide_path);
|
||||||
|
|
||||||
|
if (r) {
|
||||||
|
perror("stat");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return S_ISREG(path_stat.st_mode);
|
||||||
|
}
|
||||||
|
|
||||||
ssize_t
|
ssize_t
|
||||||
sc_pipe_read(HANDLE pipe, char *data, size_t len) {
|
read_pipe(HANDLE pipe, char *data, size_t len) {
|
||||||
DWORD r;
|
DWORD r;
|
||||||
if (!ReadFile(pipe, data, len, &r, NULL)) {
|
if (!ReadFile(pipe, data, len, &r, NULL)) {
|
||||||
return -1;
|
return -1;
|
||||||
@ -238,7 +218,7 @@ sc_pipe_read(HANDLE pipe, char *data, size_t len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_pipe_close(HANDLE pipe) {
|
close_pipe(HANDLE pipe) {
|
||||||
if (!CloseHandle(pipe)) {
|
if (!CloseHandle(pipe)) {
|
||||||
LOGW("Cannot close pipe");
|
LOGW("Cannot close pipe");
|
||||||
}
|
}
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
#include "file.h"
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "util/log.h"
|
|
||||||
|
|
||||||
char *
|
|
||||||
sc_file_get_local_path(const char *name) {
|
|
||||||
char *executable_path = sc_file_get_executable_path();
|
|
||||||
if (!executable_path) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dirname() does not work correctly everywhere, so get the parent
|
|
||||||
// directory manually.
|
|
||||||
// See <https://github.com/Genymobile/scrcpy/issues/2619>
|
|
||||||
char *p = strrchr(executable_path, SC_PATH_SEPARATOR);
|
|
||||||
if (!p) {
|
|
||||||
LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')",
|
|
||||||
executable_path, SC_PATH_SEPARATOR);
|
|
||||||
free(executable_path);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
*p = '\0'; // modify executable_path in place
|
|
||||||
char *dir = executable_path;
|
|
||||||
size_t dirlen = strlen(dir);
|
|
||||||
size_t namelen = strlen(name);
|
|
||||||
|
|
||||||
size_t len = dirlen + namelen + 2; // +2: '/' and '\0'
|
|
||||||
char *file_path = malloc(len);
|
|
||||||
if (!file_path) {
|
|
||||||
LOGE("Could not alloc path");
|
|
||||||
free(executable_path);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(file_path, dir, dirlen);
|
|
||||||
file_path[dirlen] = SC_PATH_SEPARATOR;
|
|
||||||
// namelen + 1 to copy the final '\0'
|
|
||||||
memcpy(&file_path[dirlen + 1], name, namelen + 1);
|
|
||||||
|
|
||||||
free(executable_path);
|
|
||||||
|
|
||||||
return file_path;
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
|||||||
#ifndef SC_FILE_H
|
|
||||||
#define SC_FILE_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
# define SC_PATH_SEPARATOR '\\'
|
|
||||||
#else
|
|
||||||
# define SC_PATH_SEPARATOR '/'
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef _WIN32
|
|
||||||
/**
|
|
||||||
* Indicate if an executable exists using $PATH
|
|
||||||
*
|
|
||||||
* In practice, it is only used to know if a package manager is available on
|
|
||||||
* the system. It is only implemented on Linux.
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
sc_file_executable_exists(const char *file);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the absolute path of the executable (the scrcpy binary)
|
|
||||||
*
|
|
||||||
* The result must be freed by the caller using free(). It may return NULL on
|
|
||||||
* error.
|
|
||||||
*/
|
|
||||||
char *
|
|
||||||
sc_file_get_executable_path(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the absolute path of a file in the same directory as the executable
|
|
||||||
*
|
|
||||||
* The result must be freed by the caller using free(). It may return NULL on
|
|
||||||
* error.
|
|
||||||
*/
|
|
||||||
char *
|
|
||||||
sc_file_get_local_path(const char *name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicate if the file exists and is not a directory
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
sc_file_is_regular(const char *path);
|
|
||||||
|
|
||||||
#endif
|
|
@ -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);
|
|
||||||
}
|
|
@ -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
|
|
@ -46,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;
|
||||||
@ -67,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,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;
|
||||||
@ -114,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,
|
||||||
@ -137,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
|
||||||
@ -195,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);
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -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
|
|
@ -1,25 +1,18 @@
|
|||||||
#include "process.h"
|
#include "process.h"
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
enum sc_process_result
|
|
||||||
sc_process_execute(const char *const argv[], sc_pid *pid) {
|
|
||||||
return sc_process_execute_p(argv, pid, NULL, NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_process_check_success(sc_pid pid, const char *name, bool close) {
|
process_check_success(process_t proc, const char *name, bool close) {
|
||||||
if (pid == SC_PROCESS_NONE) {
|
if (proc == PROCESS_NONE) {
|
||||||
LOGE("Could not execute \"%s\"", name);
|
LOGE("Could not execute \"%s\"", name);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
sc_exit_code exit_code = sc_process_wait(pid, close);
|
exit_code_t exit_code = process_wait(proc, close);
|
||||||
if (exit_code) {
|
if (exit_code) {
|
||||||
if (exit_code != SC_EXIT_CODE_NONE) {
|
if (exit_code != NO_EXIT_CODE) {
|
||||||
LOGE("\"%s\" returned with value %" SC_PRIexitcode, name,
|
LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code);
|
||||||
exit_code);
|
|
||||||
} else {
|
} else {
|
||||||
LOGE("\"%s\" exited unexpectedly", name);
|
LOGE("\"%s\" exited unexpectedly", name);
|
||||||
}
|
}
|
||||||
@ -28,11 +21,52 @@ sc_process_check_success(sc_pid pid, const char *name, bool close) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
get_local_file_path(const char *name) {
|
||||||
|
char *executable_path = get_executable_path();
|
||||||
|
if (!executable_path) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dirname() does not work correctly everywhere, so get the parent
|
||||||
|
// directory manually.
|
||||||
|
// See <https://github.com/Genymobile/scrcpy/issues/2619>
|
||||||
|
char *p = strrchr(executable_path, PATH_SEPARATOR);
|
||||||
|
if (!p) {
|
||||||
|
LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')",
|
||||||
|
executable_path, PATH_SEPARATOR);
|
||||||
|
free(executable_path);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*p = '\0'; // modify executable_path in place
|
||||||
|
char *dir = executable_path;
|
||||||
|
size_t dirlen = strlen(dir);
|
||||||
|
size_t namelen = strlen(name);
|
||||||
|
|
||||||
|
size_t len = dirlen + namelen + 2; // +2: '/' and '\0'
|
||||||
|
char *file_path = malloc(len);
|
||||||
|
if (!file_path) {
|
||||||
|
LOGE("Could not alloc path");
|
||||||
|
free(executable_path);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(file_path, dir, dirlen);
|
||||||
|
file_path[dirlen] = PATH_SEPARATOR;
|
||||||
|
// namelen + 1 to copy the final '\0'
|
||||||
|
memcpy(&file_path[dirlen + 1], name, namelen + 1);
|
||||||
|
|
||||||
|
free(executable_path);
|
||||||
|
|
||||||
|
return file_path;
|
||||||
|
}
|
||||||
|
|
||||||
ssize_t
|
ssize_t
|
||||||
sc_pipe_read_all(sc_pipe pipe, char *data, size_t len) {
|
read_pipe_all(pipe_t pipe, char *data, size_t len) {
|
||||||
size_t copied = 0;
|
size_t copied = 0;
|
||||||
while (len > 0) {
|
while (len > 0) {
|
||||||
ssize_t r = sc_pipe_read(pipe, data, len);
|
ssize_t r = read_pipe(pipe, data, len);
|
||||||
if (r <= 0) {
|
if (r <= 0) {
|
||||||
return copied ? (ssize_t) copied : r;
|
return copied ? (ssize_t) copied : r;
|
||||||
}
|
}
|
||||||
@ -42,80 +76,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);
|
|
||||||
}
|
|
||||||
|
@ -4,173 +4,102 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include "util/thread.h"
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
// not needed here, but winsock2.h must never be included AFTER windows.h
|
// not needed here, but winsock2.h must never be included AFTER windows.h
|
||||||
# include <winsock2.h>
|
# include <winsock2.h>
|
||||||
# include <windows.h>
|
# include <windows.h>
|
||||||
# define SC_PRIexitcode "lu"
|
# define PATH_SEPARATOR '\\'
|
||||||
|
# define PRIexitcode "lu"
|
||||||
// <https://stackoverflow.com/a/44383330/1987178>
|
// <https://stackoverflow.com/a/44383330/1987178>
|
||||||
# define SC_PRIsizet "Iu"
|
# define PRIsizet "Iu"
|
||||||
# define SC_PROCESS_NONE NULL
|
# define PROCESS_NONE NULL
|
||||||
# define SC_EXIT_CODE_NONE -1u // max value as unsigned
|
# define NO_EXIT_CODE -1u // max value as unsigned
|
||||||
typedef HANDLE sc_pid;
|
typedef HANDLE process_t;
|
||||||
typedef DWORD sc_exit_code;
|
typedef DWORD exit_code_t;
|
||||||
typedef HANDLE sc_pipe;
|
typedef HANDLE pipe_t;
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
# include <sys/types.h>
|
# include <sys/types.h>
|
||||||
# define SC_PRIsizet "zu"
|
# define PATH_SEPARATOR '/'
|
||||||
# define SC_PRIexitcode "d"
|
# define PRIsizet "zu"
|
||||||
# define SC_PROCESS_NONE -1
|
# define PRIexitcode "d"
|
||||||
# define SC_EXIT_CODE_NONE -1
|
# define PROCESS_NONE -1
|
||||||
typedef pid_t sc_pid;
|
# define NO_EXIT_CODE -1
|
||||||
typedef int sc_exit_code;
|
typedef pid_t process_t;
|
||||||
typedef int sc_pipe;
|
typedef int exit_code_t;
|
||||||
|
typedef int pipe_t;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct sc_process_listener {
|
enum process_result {
|
||||||
void (*on_terminated)(void *userdata);
|
PROCESS_SUCCESS,
|
||||||
|
PROCESS_ERROR_GENERIC,
|
||||||
|
PROCESS_ERROR_MISSING_BINARY,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// execute the command and write the result to the output parameter "process"
|
||||||
* Tool to observe process termination
|
enum process_result
|
||||||
*
|
process_execute(const char *const argv[], process_t *process);
|
||||||
* To keep things simple and multiplatform, it runs a separate thread to wait
|
|
||||||
* for process termination (without closing the process to avoid race
|
|
||||||
* conditions).
|
|
||||||
*
|
|
||||||
* It allows a caller to block until the process is terminated (with a
|
|
||||||
* timeout), and to be notified asynchronously from the observer thread.
|
|
||||||
*
|
|
||||||
* The process is not owned by the observer (the observer will never close it).
|
|
||||||
*/
|
|
||||||
struct sc_process_observer {
|
|
||||||
sc_pid pid;
|
|
||||||
|
|
||||||
sc_mutex mutex;
|
enum process_result
|
||||||
sc_cond cond_terminated;
|
process_execute_redirect(const char *const argv[], process_t *process,
|
||||||
bool terminated;
|
pipe_t *pipe_stdin, pipe_t *pipe_stdout,
|
||||||
|
pipe_t *pipe_stderr);
|
||||||
|
|
||||||
sc_thread thread;
|
|
||||||
const struct sc_process_listener *listener;
|
|
||||||
void *listener_userdata;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum sc_process_result {
|
|
||||||
SC_PROCESS_SUCCESS,
|
|
||||||
SC_PROCESS_ERROR_GENERIC,
|
|
||||||
SC_PROCESS_ERROR_MISSING_BINARY,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the command and write the process id to `pid`
|
|
||||||
*/
|
|
||||||
enum sc_process_result
|
|
||||||
sc_process_execute(const char *const argv[], sc_pid *pid);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the command and write the process id to `pid`
|
|
||||||
*
|
|
||||||
* If not NULL, provide a pipe for stdin (`pin`), stdout (`pout`) and stderr
|
|
||||||
* (`perr`).
|
|
||||||
*/
|
|
||||||
enum sc_process_result
|
|
||||||
sc_process_execute_p(const char *const argv[], sc_pid *pid,
|
|
||||||
sc_pipe *pin, sc_pipe *pout, sc_pipe *perr);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Kill the process
|
|
||||||
*/
|
|
||||||
bool
|
bool
|
||||||
sc_process_terminate(sc_pid pid);
|
process_terminate(process_t pid);
|
||||||
|
|
||||||
/**
|
// kill the process
|
||||||
* Wait and close the process (similar to waitpid())
|
bool
|
||||||
*
|
process_terminate(process_t pid);
|
||||||
* The `close` flag indicates if the process must be _closed_ (reaped) (passing
|
|
||||||
* false is equivalent to enable WNOWAIT in waitid()).
|
|
||||||
*/
|
|
||||||
sc_exit_code
|
|
||||||
sc_process_wait(sc_pid pid, bool close);
|
|
||||||
|
|
||||||
/**
|
// wait and close the process (like waitpid())
|
||||||
* Close (reap) the process
|
// the "close" flag indicates if the process must be "closed" (reaped)
|
||||||
*
|
// (passing false is equivalent to enable WNOWAIT in waitid())
|
||||||
* Semantically:
|
exit_code_t
|
||||||
* sc_process_wait(close) = sc_process_wait(noclose) + sc_process_close()
|
process_wait(process_t pid, bool close);
|
||||||
*/
|
|
||||||
|
// close the process
|
||||||
|
//
|
||||||
|
// Semantically, process_wait(close) = process_wait(noclose) + process_close
|
||||||
void
|
void
|
||||||
sc_process_close(sc_pid pid);
|
process_close(process_t pid);
|
||||||
|
|
||||||
/**
|
// convenience function to wait for a successful process execution
|
||||||
* Convenience function to wait for a successful process execution
|
// automatically log process errors with the provided process name
|
||||||
*
|
|
||||||
* Automatically log process errors with the provided process name.
|
|
||||||
*/
|
|
||||||
bool
|
bool
|
||||||
sc_process_check_success(sc_pid pid, const char *name, bool close);
|
process_check_success(process_t proc, const char *name, bool close);
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
// only used to find package manager, not implemented for Windows
|
||||||
|
bool
|
||||||
|
search_executable(const char *file);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// return the absolute path of the executable (the scrcpy binary)
|
||||||
|
// may be NULL on error; to be freed by free()
|
||||||
|
char *
|
||||||
|
get_executable_path(void);
|
||||||
|
|
||||||
|
// Return the absolute path of a file in the same directory as he executable.
|
||||||
|
// May be NULL on error. To be freed by free().
|
||||||
|
char *
|
||||||
|
get_local_file_path(const char *name);
|
||||||
|
|
||||||
|
// returns true if the file exists and is not a directory
|
||||||
|
bool
|
||||||
|
is_regular_file(const char *path);
|
||||||
|
|
||||||
/**
|
|
||||||
* Read from the pipe
|
|
||||||
*
|
|
||||||
* Same semantic as read().
|
|
||||||
*/
|
|
||||||
ssize_t
|
ssize_t
|
||||||
sc_pipe_read(sc_pipe pipe, char *data, size_t len);
|
read_pipe(pipe_t pipe, char *data, size_t len);
|
||||||
|
|
||||||
/**
|
|
||||||
* Read exactly `len` chars from a pipe (unless EOF)
|
|
||||||
*/
|
|
||||||
ssize_t
|
ssize_t
|
||||||
sc_pipe_read_all(sc_pipe pipe, char *data, size_t len);
|
read_pipe_all(pipe_t pipe, char *data, size_t len);
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the pipe
|
|
||||||
*/
|
|
||||||
void
|
void
|
||||||
sc_pipe_close(sc_pipe pipe);
|
close_pipe(pipe_t pipe);
|
||||||
|
|
||||||
/**
|
|
||||||
* Start observing process
|
|
||||||
*
|
|
||||||
* The listener is optional. If set, its callback will be called from the
|
|
||||||
* observer thread once the process is terminated.
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
sc_process_observer_init(struct sc_process_observer *observer, sc_pid pid,
|
|
||||||
const struct sc_process_listener *listener,
|
|
||||||
void *listener_userdata);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wait for process termination until a deadline
|
|
||||||
*
|
|
||||||
* Return true if the process is already terminated. Return false if the
|
|
||||||
* process terminatation has not been detected yet (however, it may have
|
|
||||||
* terminated in the meantime).
|
|
||||||
*
|
|
||||||
* To wait without timeout/deadline, just use sc_process_wait() instead.
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
sc_process_observer_timedwait(struct sc_process_observer *observer,
|
|
||||||
sc_tick deadline);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Join the observer thread
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_process_observer_join(struct sc_process_observer *observer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroy the observer
|
|
||||||
*
|
|
||||||
* This does not close the associated process.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_process_observer_destroy(struct sc_process_observer *observer);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
#include "process_intr.h"
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_process_check_success_intr(struct sc_intr *intr, sc_pid pid,
|
|
||||||
const char *name) {
|
|
||||||
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);
|
|
||||||
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;
|
|
||||||
}
|
|
@ -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);
|
|
||||||
|
|
||||||
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
|
|
@ -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
|
|
@ -1,11 +1,9 @@
|
|||||||
#include "str.h"
|
#include "str_util.h"
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "util/strbuf.h"
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
# include <windows.h>
|
# include <windows.h>
|
||||||
@ -13,7 +11,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
sc_strncpy(char *dest, const char *src, size_t n) {
|
xstrncpy(char *dest, const char *src, size_t n) {
|
||||||
size_t i;
|
size_t i;
|
||||||
for (i = 0; i < n - 1 && src[i] != '\0'; ++i)
|
for (i = 0; i < n - 1 && src[i] != '\0'; ++i)
|
||||||
dest[i] = src[i];
|
dest[i] = src[i];
|
||||||
@ -23,7 +21,7 @@ sc_strncpy(char *dest, const char *src, size_t n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
sc_str_join(char *dst, const char *const tokens[], char sep, size_t n) {
|
xstrjoin(char *dst, const char *const tokens[], char sep, size_t n) {
|
||||||
const char *const *remaining = tokens;
|
const char *const *remaining = tokens;
|
||||||
const char *token = *remaining++;
|
const char *token = *remaining++;
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
@ -33,7 +31,7 @@ sc_str_join(char *dst, const char *const tokens[], char sep, size_t n) {
|
|||||||
if (i == n)
|
if (i == n)
|
||||||
goto truncated;
|
goto truncated;
|
||||||
}
|
}
|
||||||
size_t w = sc_strncpy(dst + i, token, n - i);
|
size_t w = xstrncpy(dst + i, token, n - i);
|
||||||
if (w >= n - i)
|
if (w >= n - i)
|
||||||
goto truncated;
|
goto truncated;
|
||||||
i += w;
|
i += w;
|
||||||
@ -47,7 +45,7 @@ truncated:
|
|||||||
}
|
}
|
||||||
|
|
||||||
char *
|
char *
|
||||||
sc_str_quote(const char *src) {
|
strquote(const char *src) {
|
||||||
size_t len = strlen(src);
|
size_t len = strlen(src);
|
||||||
char *quoted = malloc(len + 3);
|
char *quoted = malloc(len + 3);
|
||||||
if (!quoted) {
|
if (!quoted) {
|
||||||
@ -61,7 +59,7 @@ sc_str_quote(const char *src) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_str_parse_integer(const char *s, long *out) {
|
parse_integer(const char *s, long *out) {
|
||||||
char *endptr;
|
char *endptr;
|
||||||
if (*s == '\0') {
|
if (*s == '\0') {
|
||||||
return false;
|
return false;
|
||||||
@ -80,8 +78,7 @@ sc_str_parse_integer(const char *s, long *out) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
sc_str_parse_integers(const char *s, const char sep, size_t max_items,
|
parse_integers(const char *s, const char sep, size_t max_items, long *out) {
|
||||||
long *out) {
|
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
char *endptr;
|
char *endptr;
|
||||||
do {
|
do {
|
||||||
@ -110,7 +107,7 @@ sc_str_parse_integers(const char *s, const char sep, size_t max_items,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_str_parse_integer_with_suffix(const char *s, long *out) {
|
parse_integer_with_suffix(const char *s, long *out) {
|
||||||
char *endptr;
|
char *endptr;
|
||||||
if (*s == '\0') {
|
if (*s == '\0') {
|
||||||
return false;
|
return false;
|
||||||
@ -144,7 +141,7 @@ sc_str_parse_integer_with_suffix(const char *s, long *out) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_str_list_contains(const char *list, char sep, const char *s) {
|
strlist_contains(const char *list, char sep, const char *s) {
|
||||||
char *p;
|
char *p;
|
||||||
do {
|
do {
|
||||||
p = strchr(list, sep);
|
p = strchr(list, sep);
|
||||||
@ -162,7 +159,7 @@ sc_str_list_contains(const char *list, char sep, const char *s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
sc_str_utf8_truncation_index(const char *utf8, size_t max_len) {
|
utf8_truncation_index(const char *utf8, size_t max_len) {
|
||||||
size_t len = strlen(utf8);
|
size_t len = strlen(utf8);
|
||||||
if (len <= max_len) {
|
if (len <= max_len) {
|
||||||
return len;
|
return len;
|
||||||
@ -180,7 +177,7 @@ sc_str_utf8_truncation_index(const char *utf8, size_t max_len) {
|
|||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
wchar_t *
|
wchar_t *
|
||||||
sc_str_to_wchars(const char *utf8) {
|
utf8_to_wide_char(const char *utf8) {
|
||||||
int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
|
int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
|
||||||
if (!len) {
|
if (!len) {
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -196,7 +193,7 @@ sc_str_to_wchars(const char *utf8) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char *
|
char *
|
||||||
sc_str_from_wchars(const wchar_t *ws) {
|
utf8_from_wide_char(const wchar_t *ws) {
|
||||||
int len = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL);
|
int len = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL);
|
||||||
if (!len) {
|
if (!len) {
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -212,93 +209,3 @@ sc_str_from_wchars(const wchar_t *ws) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
char *
|
|
||||||
sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent) {
|
|
||||||
assert(indent < columns);
|
|
||||||
|
|
||||||
struct sc_strbuf buf;
|
|
||||||
|
|
||||||
// The output string should not be much longer than the input string (just
|
|
||||||
// a few '\n' added), so this initial capacity should hopefully almost
|
|
||||||
// always avoid internal realloc() in string buffer
|
|
||||||
size_t cap = strlen(input) * 3 / 2;
|
|
||||||
|
|
||||||
if (!sc_strbuf_init(&buf, cap)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define APPEND(S,N) if (!sc_strbuf_append(&buf, S, N)) goto error
|
|
||||||
#define APPEND_CHAR(C) if (!sc_strbuf_append_char(&buf, C)) goto error
|
|
||||||
#define APPEND_N(C,N) if (!sc_strbuf_append_n(&buf, C, N)) goto error
|
|
||||||
#define APPEND_INDENT() if (indent) APPEND_N(' ', indent)
|
|
||||||
|
|
||||||
APPEND_INDENT();
|
|
||||||
|
|
||||||
// The last separator encountered, it must be inserted only conditionnaly,
|
|
||||||
// depending on the next token
|
|
||||||
char pending = 0;
|
|
||||||
|
|
||||||
// col tracks the current column in the current line
|
|
||||||
size_t col = indent;
|
|
||||||
while (*input) {
|
|
||||||
size_t sep_idx = strcspn(input, "\n ");
|
|
||||||
size_t new_col = col + sep_idx;
|
|
||||||
if (pending == ' ') {
|
|
||||||
// The pending space counts
|
|
||||||
++new_col;
|
|
||||||
}
|
|
||||||
bool wrap = new_col > columns;
|
|
||||||
|
|
||||||
char sep = input[sep_idx];
|
|
||||||
if (sep == ' ')
|
|
||||||
sep = ' ';
|
|
||||||
|
|
||||||
if (wrap) {
|
|
||||||
APPEND_CHAR('\n');
|
|
||||||
APPEND_INDENT();
|
|
||||||
col = indent;
|
|
||||||
} else if (pending) {
|
|
||||||
APPEND_CHAR(pending);
|
|
||||||
++col;
|
|
||||||
if (pending == '\n')
|
|
||||||
{
|
|
||||||
APPEND_INDENT();
|
|
||||||
col = indent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sep_idx) {
|
|
||||||
APPEND(input, sep_idx);
|
|
||||||
col += sep_idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
pending = sep;
|
|
||||||
|
|
||||||
input += sep_idx;
|
|
||||||
if (*input != '\0') {
|
|
||||||
// Skip the separator
|
|
||||||
++input;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pending)
|
|
||||||
APPEND_CHAR(pending);
|
|
||||||
|
|
||||||
return buf.s;
|
|
||||||
|
|
||||||
error:
|
|
||||||
free(buf.s);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
sc_str_truncate(char *data, size_t len, const char *endchars) {
|
|
||||||
data[len - 1] = '\0';
|
|
||||||
char *eol = strpbrk(data, endchars);
|
|
||||||
if (eol) {
|
|
||||||
*eol = '\0';
|
|
||||||
len = eol - data;
|
|
||||||
}
|
|
||||||
return len;
|
|
||||||
}
|
|
65
app/src/util/str_util.h
Normal file
65
app/src/util/str_util.h
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
#ifndef STRUTIL_H
|
||||||
|
#define STRUTIL_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
// like strncpy, except:
|
||||||
|
// - it copies at most n-1 chars
|
||||||
|
// - the dest string is nul-terminated
|
||||||
|
// - it does not write useless bytes if strlen(src) < n
|
||||||
|
// - it returns the number of chars actually written (max n-1) if src has
|
||||||
|
// been copied completely, or n if src has been truncated
|
||||||
|
size_t
|
||||||
|
xstrncpy(char *dest, const char *src, size_t n);
|
||||||
|
|
||||||
|
// join tokens by sep into dst
|
||||||
|
// returns the number of chars actually written (max n-1) if no truncation
|
||||||
|
// occurred, or n if truncated
|
||||||
|
size_t
|
||||||
|
xstrjoin(char *dst, const char *const tokens[], char sep, size_t n);
|
||||||
|
|
||||||
|
// quote a string
|
||||||
|
// returns the new allocated string, to be freed by the caller
|
||||||
|
char *
|
||||||
|
strquote(const char *src);
|
||||||
|
|
||||||
|
// parse s as an integer into value
|
||||||
|
// returns true if the conversion succeeded, false otherwise
|
||||||
|
bool
|
||||||
|
parse_integer(const char *s, long *out);
|
||||||
|
|
||||||
|
// parse s as integers separated by sep (for example '1234:2000')
|
||||||
|
// returns the number of integers on success, 0 on failure
|
||||||
|
size_t
|
||||||
|
parse_integers(const char *s, const char sep, size_t max_items, long *out);
|
||||||
|
|
||||||
|
// parse s as an integer into value
|
||||||
|
// like parse_integer(), but accept 'k'/'K' (x1000) and 'm'/'M' (x1000000) as
|
||||||
|
// suffix
|
||||||
|
// returns true if the conversion succeeded, false otherwise
|
||||||
|
bool
|
||||||
|
parse_integer_with_suffix(const char *s, long *out);
|
||||||
|
|
||||||
|
// search s in the list separated by sep
|
||||||
|
// for example, strlist_contains("a,bc,def", ',', "bc") returns true
|
||||||
|
bool
|
||||||
|
strlist_contains(const char *list, char sep, const char *s);
|
||||||
|
|
||||||
|
// return the index to truncate a UTF-8 string at a valid position
|
||||||
|
size_t
|
||||||
|
utf8_truncation_index(const char *utf8, size_t max_len);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// convert a UTF-8 string to a wchar_t string
|
||||||
|
// returns the new allocated string, to be freed by the caller
|
||||||
|
wchar_t *
|
||||||
|
utf8_to_wide_char(const char *utf8);
|
||||||
|
|
||||||
|
char *
|
||||||
|
utf8_from_wide_char(const wchar_t *s);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
@ -1,87 +0,0 @@
|
|||||||
#include "strbuf.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap) {
|
|
||||||
buf->s = malloc(init_cap + 1); // +1 for '\0'
|
|
||||||
if (!buf->s) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf->len = 0;
|
|
||||||
buf->cap = init_cap;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
sc_strbuf_reserve(struct sc_strbuf *buf, size_t len) {
|
|
||||||
if (buf->len + len > buf->cap) {
|
|
||||||
size_t new_cap = buf->cap * 3 / 2 + len;
|
|
||||||
char *s = realloc(buf->s, new_cap + 1); // +1 for '\0'
|
|
||||||
if (!s) {
|
|
||||||
// Leave the old buf->s
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
buf->s = s;
|
|
||||||
buf->cap = new_cap;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len) {
|
|
||||||
assert(s);
|
|
||||||
assert(*s);
|
|
||||||
assert(strlen(s) >= len);
|
|
||||||
if (!sc_strbuf_reserve(buf, len)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(&buf->s[buf->len], s, len);
|
|
||||||
buf->len += len;
|
|
||||||
buf->s[buf->len] = '\0';
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_strbuf_append_char(struct sc_strbuf *buf, const char c) {
|
|
||||||
if (!sc_strbuf_reserve(buf, 1)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf->s[buf->len] = c;
|
|
||||||
buf->len ++;
|
|
||||||
buf->s[buf->len] = '\0';
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n) {
|
|
||||||
if (!sc_strbuf_reserve(buf, n)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(&buf->s[buf->len], c, n);
|
|
||||||
buf->len += n;
|
|
||||||
buf->s[buf->len] = '\0';
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_strbuf_shrink(struct sc_strbuf *buf) {
|
|
||||||
assert(buf->len <= buf->cap);
|
|
||||||
if (buf->len != buf->cap) {
|
|
||||||
char *s = realloc(buf->s, buf->len + 1); // +1 for '\0'
|
|
||||||
assert(s); // decreasing the size may not fail
|
|
||||||
buf->s = s;
|
|
||||||
buf->cap = buf->len;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
#ifndef SC_STRBUF_H
|
|
||||||
#define SC_STRBUF_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
struct sc_strbuf {
|
|
||||||
char *s;
|
|
||||||
size_t len;
|
|
||||||
size_t cap;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the string buffer
|
|
||||||
*
|
|
||||||
* `buf->s` must be manually freed by the caller.
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append a string
|
|
||||||
*
|
|
||||||
* Append `len` characters from `s` to the buffer.
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append a char
|
|
||||||
*
|
|
||||||
* Append a single character to the buffer.
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
sc_strbuf_append_char(struct sc_strbuf *buf, const char c);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append a char `n` times
|
|
||||||
*
|
|
||||||
* Append the same characters `n` times to the buffer.
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append a NUL-terminated string
|
|
||||||
*/
|
|
||||||
static inline bool
|
|
||||||
sc_strbuf_append_str(struct sc_strbuf *buf, const char *s) {
|
|
||||||
return sc_strbuf_append(buf, s, strlen(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append a static string
|
|
||||||
*
|
|
||||||
* Append a string whose size is known at compile time (for
|
|
||||||
* example a string literal).
|
|
||||||
*/
|
|
||||||
#define sc_strbuf_append_staticstr(BUF, S) \
|
|
||||||
sc_strbuf_append(BUF, S, sizeof(S) - 1)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shrink the buffer capacity to its current length
|
|
||||||
*
|
|
||||||
* This resizes `buf->s` to fit the content.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_strbuf_shrink(struct sc_strbuf *buf);
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,51 +0,0 @@
|
|||||||
#include "term.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
# include <windows.h>
|
|
||||||
#else
|
|
||||||
# include <unistd.h>
|
|
||||||
# include <sys/ioctl.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_term_get_size(unsigned *rows, unsigned *cols) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
|
||||||
|
|
||||||
bool ok =
|
|
||||||
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
|
|
||||||
if (!ok) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rows) {
|
|
||||||
assert(csbi.srWindow.Bottom >= csbi.srWindow.Top);
|
|
||||||
*rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cols) {
|
|
||||||
assert(csbi.srWindow.Right >= csbi.srWindow.Left);
|
|
||||||
*cols = csbi.srWindow.Right - csbi.srWindow.Left + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
#else
|
|
||||||
struct winsize ws;
|
|
||||||
int r = ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
|
|
||||||
if (r == -1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rows) {
|
|
||||||
*rows = ws.ws_row;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cols) {
|
|
||||||
*cols = ws.ws_col;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
#ifndef SC_TERM_H
|
|
||||||
#define SC_TERM_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the terminal dimensions
|
|
||||||
*
|
|
||||||
* Return false if the dimensions could not be retrieved.
|
|
||||||
*
|
|
||||||
* Otherwise, return true, and:
|
|
||||||
* - if `rows` is not NULL, then the number of rows is written to `*rows`.
|
|
||||||
* - if `columns` is not NULL, then the number of columns is written to
|
|
||||||
* `*columns`.
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
sc_term_get_size(unsigned *rows, unsigned *cols);
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,8 +1,6 @@
|
|||||||
#ifndef SC_TICK_H
|
#ifndef SC_TICK_H
|
||||||
#define SC_TICK_H
|
#define SC_TICK_H
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
typedef int64_t sc_tick;
|
typedef int64_t sc_tick;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#include "v4l2_sink.h"
|
#include "v4l2_sink.h"
|
||||||
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
/** Downcast frame_sink to sc_v4l2_sink */
|
/** Downcast frame_sink to sc_v4l2_sink */
|
||||||
#define DOWNCAST(SINK) container_of(SINK, struct sc_v4l2_sink, frame_sink)
|
#define DOWNCAST(SINK) container_of(SINK, struct sc_v4l2_sink, frame_sink)
|
||||||
@ -21,7 +21,7 @@ find_muxer(const char *name) {
|
|||||||
oformat = av_oformat_next(oformat);
|
oformat = av_oformat_next(oformat);
|
||||||
#endif
|
#endif
|
||||||
// until null or containing the requested name
|
// until null or containing the requested name
|
||||||
} while (oformat && !sc_str_list_contains(oformat->name, ',', name));
|
} while (oformat && !strlist_contains(oformat->name, ',', name));
|
||||||
return oformat;
|
return oformat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,7 +359,7 @@ sc_v4l2_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
||||||
struct sc_size frame_size, sc_tick buffering_time) {
|
struct size frame_size, sc_tick buffering_time) {
|
||||||
vs->device_name = strdup(device_name);
|
vs->device_name = strdup(device_name);
|
||||||
if (!vs->device_name) {
|
if (!vs->device_name) {
|
||||||
LOGE("Could not strdup v4l2 device name");
|
LOGE("Could not strdup v4l2 device name");
|
||||||
|
@ -18,7 +18,7 @@ struct sc_v4l2_sink {
|
|||||||
AVCodecContext *encoder_ctx;
|
AVCodecContext *encoder_ctx;
|
||||||
|
|
||||||
char *device_name;
|
char *device_name;
|
||||||
struct sc_size frame_size;
|
struct size frame_size;
|
||||||
sc_tick buffering_time;
|
sc_tick buffering_time;
|
||||||
|
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
@ -34,7 +34,7 @@ struct sc_v4l2_sink {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
||||||
struct sc_size frame_size, sc_tick buffering_time);
|
struct size frame_size, sc_tick buffering_time);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);
|
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);
|
||||||
|
@ -1,381 +0,0 @@
|
|||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "util/str.h"
|
|
||||||
|
|
||||||
static void test_strncpy_simple(void) {
|
|
||||||
char s[] = "xxxxxxxxxx";
|
|
||||||
size_t w = sc_strncpy(s, "abcdef", sizeof(s));
|
|
||||||
|
|
||||||
// returns strlen of copied string
|
|
||||||
assert(w == 6);
|
|
||||||
|
|
||||||
// is nul-terminated
|
|
||||||
assert(s[6] == '\0');
|
|
||||||
|
|
||||||
// does not write useless bytes
|
|
||||||
assert(s[7] == 'x');
|
|
||||||
|
|
||||||
// copies the content as expected
|
|
||||||
assert(!strcmp("abcdef", s));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_strncpy_just_fit(void) {
|
|
||||||
char s[] = "xxxxxx";
|
|
||||||
size_t w = sc_strncpy(s, "abcdef", sizeof(s));
|
|
||||||
|
|
||||||
// returns strlen of copied string
|
|
||||||
assert(w == 6);
|
|
||||||
|
|
||||||
// is nul-terminated
|
|
||||||
assert(s[6] == '\0');
|
|
||||||
|
|
||||||
// copies the content as expected
|
|
||||||
assert(!strcmp("abcdef", s));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_strncpy_truncated(void) {
|
|
||||||
char s[] = "xxx";
|
|
||||||
size_t w = sc_strncpy(s, "abcdef", sizeof(s));
|
|
||||||
|
|
||||||
// returns 'n' (sizeof(s))
|
|
||||||
assert(w == 4);
|
|
||||||
|
|
||||||
// is nul-terminated
|
|
||||||
assert(s[3] == '\0');
|
|
||||||
|
|
||||||
// copies the content as expected
|
|
||||||
assert(!strncmp("abcdef", s, 3));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_join_simple(void) {
|
|
||||||
const char *const tokens[] = { "abc", "de", "fghi", NULL };
|
|
||||||
char s[] = "xxxxxxxxxxxxxx";
|
|
||||||
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
|
|
||||||
|
|
||||||
// returns strlen of concatenation
|
|
||||||
assert(w == 11);
|
|
||||||
|
|
||||||
// is nul-terminated
|
|
||||||
assert(s[11] == '\0');
|
|
||||||
|
|
||||||
// does not write useless bytes
|
|
||||||
assert(s[12] == 'x');
|
|
||||||
|
|
||||||
// copies the content as expected
|
|
||||||
assert(!strcmp("abc de fghi", s));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_join_just_fit(void) {
|
|
||||||
const char *const tokens[] = { "abc", "de", "fghi", NULL };
|
|
||||||
char s[] = "xxxxxxxxxxx";
|
|
||||||
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
|
|
||||||
|
|
||||||
// returns strlen of concatenation
|
|
||||||
assert(w == 11);
|
|
||||||
|
|
||||||
// is nul-terminated
|
|
||||||
assert(s[11] == '\0');
|
|
||||||
|
|
||||||
// copies the content as expected
|
|
||||||
assert(!strcmp("abc de fghi", s));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_join_truncated_in_token(void) {
|
|
||||||
const char *const tokens[] = { "abc", "de", "fghi", NULL };
|
|
||||||
char s[] = "xxxxx";
|
|
||||||
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
|
|
||||||
|
|
||||||
// returns 'n' (sizeof(s))
|
|
||||||
assert(w == 6);
|
|
||||||
|
|
||||||
// is nul-terminated
|
|
||||||
assert(s[5] == '\0');
|
|
||||||
|
|
||||||
// copies the content as expected
|
|
||||||
assert(!strcmp("abc d", s));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_join_truncated_before_sep(void) {
|
|
||||||
const char *const tokens[] = { "abc", "de", "fghi", NULL };
|
|
||||||
char s[] = "xxxxxx";
|
|
||||||
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
|
|
||||||
|
|
||||||
// returns 'n' (sizeof(s))
|
|
||||||
assert(w == 7);
|
|
||||||
|
|
||||||
// is nul-terminated
|
|
||||||
assert(s[6] == '\0');
|
|
||||||
|
|
||||||
// copies the content as expected
|
|
||||||
assert(!strcmp("abc de", s));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_join_truncated_after_sep(void) {
|
|
||||||
const char *const tokens[] = { "abc", "de", "fghi", NULL };
|
|
||||||
char s[] = "xxxxxxx";
|
|
||||||
size_t w = sc_str_join(s, tokens, ' ', sizeof(s));
|
|
||||||
|
|
||||||
// returns 'n' (sizeof(s))
|
|
||||||
assert(w == 8);
|
|
||||||
|
|
||||||
// is nul-terminated
|
|
||||||
assert(s[7] == '\0');
|
|
||||||
|
|
||||||
// copies the content as expected
|
|
||||||
assert(!strcmp("abc de ", s));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_quote(void) {
|
|
||||||
const char *s = "abcde";
|
|
||||||
char *out = sc_str_quote(s);
|
|
||||||
|
|
||||||
// add '"' at the beginning and the end
|
|
||||||
assert(!strcmp("\"abcde\"", out));
|
|
||||||
|
|
||||||
free(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_utf8_truncate(void) {
|
|
||||||
const char *s = "aÉbÔc";
|
|
||||||
assert(strlen(s) == 7); // É and Ô are 2 bytes-wide
|
|
||||||
|
|
||||||
size_t count;
|
|
||||||
|
|
||||||
count = sc_str_utf8_truncation_index(s, 1);
|
|
||||||
assert(count == 1);
|
|
||||||
|
|
||||||
count = sc_str_utf8_truncation_index(s, 2);
|
|
||||||
assert(count == 1); // É is 2 bytes-wide
|
|
||||||
|
|
||||||
count = sc_str_utf8_truncation_index(s, 3);
|
|
||||||
assert(count == 3);
|
|
||||||
|
|
||||||
count = sc_str_utf8_truncation_index(s, 4);
|
|
||||||
assert(count == 4);
|
|
||||||
|
|
||||||
count = sc_str_utf8_truncation_index(s, 5);
|
|
||||||
assert(count == 4); // Ô is 2 bytes-wide
|
|
||||||
|
|
||||||
count = sc_str_utf8_truncation_index(s, 6);
|
|
||||||
assert(count == 6);
|
|
||||||
|
|
||||||
count = sc_str_utf8_truncation_index(s, 7);
|
|
||||||
assert(count == 7);
|
|
||||||
|
|
||||||
count = sc_str_utf8_truncation_index(s, 8);
|
|
||||||
assert(count == 7); // no more chars
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_parse_integer(void) {
|
|
||||||
long value;
|
|
||||||
bool ok = sc_str_parse_integer("1234", &value);
|
|
||||||
assert(ok);
|
|
||||||
assert(value == 1234);
|
|
||||||
|
|
||||||
ok = sc_str_parse_integer("-1234", &value);
|
|
||||||
assert(ok);
|
|
||||||
assert(value == -1234);
|
|
||||||
|
|
||||||
ok = sc_str_parse_integer("1234k", &value);
|
|
||||||
assert(!ok);
|
|
||||||
|
|
||||||
ok = sc_str_parse_integer("123456789876543212345678987654321", &value);
|
|
||||||
assert(!ok); // out-of-range
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_parse_integers(void) {
|
|
||||||
long values[5];
|
|
||||||
|
|
||||||
size_t count = sc_str_parse_integers("1234", ':', 5, values);
|
|
||||||
assert(count == 1);
|
|
||||||
assert(values[0] == 1234);
|
|
||||||
|
|
||||||
count = sc_str_parse_integers("1234:5678", ':', 5, values);
|
|
||||||
assert(count == 2);
|
|
||||||
assert(values[0] == 1234);
|
|
||||||
assert(values[1] == 5678);
|
|
||||||
|
|
||||||
count = sc_str_parse_integers("1234:5678", ':', 2, values);
|
|
||||||
assert(count == 2);
|
|
||||||
assert(values[0] == 1234);
|
|
||||||
assert(values[1] == 5678);
|
|
||||||
|
|
||||||
count = sc_str_parse_integers("1234:-5678", ':', 2, values);
|
|
||||||
assert(count == 2);
|
|
||||||
assert(values[0] == 1234);
|
|
||||||
assert(values[1] == -5678);
|
|
||||||
|
|
||||||
count = sc_str_parse_integers("1:2:3:4:5", ':', 5, values);
|
|
||||||
assert(count == 5);
|
|
||||||
assert(values[0] == 1);
|
|
||||||
assert(values[1] == 2);
|
|
||||||
assert(values[2] == 3);
|
|
||||||
assert(values[3] == 4);
|
|
||||||
assert(values[4] == 5);
|
|
||||||
|
|
||||||
count = sc_str_parse_integers("1234:5678", ':', 1, values);
|
|
||||||
assert(count == 0); // max_items == 1
|
|
||||||
|
|
||||||
count = sc_str_parse_integers("1:2:3:4:5", ':', 3, values);
|
|
||||||
assert(count == 0); // max_items == 3
|
|
||||||
|
|
||||||
count = sc_str_parse_integers(":1234", ':', 5, values);
|
|
||||||
assert(count == 0); // invalid
|
|
||||||
|
|
||||||
count = sc_str_parse_integers("1234:", ':', 5, values);
|
|
||||||
assert(count == 0); // invalid
|
|
||||||
|
|
||||||
count = sc_str_parse_integers("1234:", ':', 1, values);
|
|
||||||
assert(count == 0); // invalid, even when max_items == 1
|
|
||||||
|
|
||||||
count = sc_str_parse_integers("1234::5678", ':', 5, values);
|
|
||||||
assert(count == 0); // invalid
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_parse_integer_with_suffix(void) {
|
|
||||||
long value;
|
|
||||||
bool ok = sc_str_parse_integer_with_suffix("1234", &value);
|
|
||||||
assert(ok);
|
|
||||||
assert(value == 1234);
|
|
||||||
|
|
||||||
ok = sc_str_parse_integer_with_suffix("-1234", &value);
|
|
||||||
assert(ok);
|
|
||||||
assert(value == -1234);
|
|
||||||
|
|
||||||
ok = sc_str_parse_integer_with_suffix("1234k", &value);
|
|
||||||
assert(ok);
|
|
||||||
assert(value == 1234000);
|
|
||||||
|
|
||||||
ok = sc_str_parse_integer_with_suffix("1234m", &value);
|
|
||||||
assert(ok);
|
|
||||||
assert(value == 1234000000);
|
|
||||||
|
|
||||||
ok = sc_str_parse_integer_with_suffix("-1234k", &value);
|
|
||||||
assert(ok);
|
|
||||||
assert(value == -1234000);
|
|
||||||
|
|
||||||
ok = sc_str_parse_integer_with_suffix("-1234m", &value);
|
|
||||||
assert(ok);
|
|
||||||
assert(value == -1234000000);
|
|
||||||
|
|
||||||
ok = sc_str_parse_integer_with_suffix("123456789876543212345678987654321", &value);
|
|
||||||
assert(!ok); // out-of-range
|
|
||||||
|
|
||||||
char buf[32];
|
|
||||||
|
|
||||||
sprintf(buf, "%ldk", LONG_MAX / 2000);
|
|
||||||
ok = sc_str_parse_integer_with_suffix(buf, &value);
|
|
||||||
assert(ok);
|
|
||||||
assert(value == LONG_MAX / 2000 * 1000);
|
|
||||||
|
|
||||||
sprintf(buf, "%ldm", LONG_MAX / 2000);
|
|
||||||
ok = sc_str_parse_integer_with_suffix(buf, &value);
|
|
||||||
assert(!ok);
|
|
||||||
|
|
||||||
sprintf(buf, "%ldk", LONG_MIN / 2000);
|
|
||||||
ok = sc_str_parse_integer_with_suffix(buf, &value);
|
|
||||||
assert(ok);
|
|
||||||
assert(value == LONG_MIN / 2000 * 1000);
|
|
||||||
|
|
||||||
sprintf(buf, "%ldm", LONG_MIN / 2000);
|
|
||||||
ok = sc_str_parse_integer_with_suffix(buf, &value);
|
|
||||||
assert(!ok);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_strlist_contains(void) {
|
|
||||||
assert(sc_str_list_contains("a,bc,def", ',', "bc"));
|
|
||||||
assert(!sc_str_list_contains("a,bc,def", ',', "b"));
|
|
||||||
assert(sc_str_list_contains("", ',', ""));
|
|
||||||
assert(sc_str_list_contains("abc,", ',', ""));
|
|
||||||
assert(sc_str_list_contains(",abc", ',', ""));
|
|
||||||
assert(sc_str_list_contains("abc,,def", ',', ""));
|
|
||||||
assert(!sc_str_list_contains("abc", ',', ""));
|
|
||||||
assert(sc_str_list_contains(",,|x", '|', ",,"));
|
|
||||||
assert(sc_str_list_contains("xyz", '\0', "xyz"));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_wrap_lines(void) {
|
|
||||||
const char *s = "This is a text to test line wrapping. The lines must be "
|
|
||||||
"wrapped at a space or a line break.\n"
|
|
||||||
"\n"
|
|
||||||
"This rectangle must remains a rectangle because it is "
|
|
||||||
"drawn in lines having lengths lower than the specified "
|
|
||||||
"number of columns:\n"
|
|
||||||
" +----+\n"
|
|
||||||
" | |\n"
|
|
||||||
" +----+\n";
|
|
||||||
|
|
||||||
// |---- 1 1 2 2|
|
|
||||||
// |0 5 0 5 0 3| <-- 24 columns
|
|
||||||
const char *expected = " This is a text to\n"
|
|
||||||
" test line wrapping.\n"
|
|
||||||
" The lines must be\n"
|
|
||||||
" wrapped at a space\n"
|
|
||||||
" or a line break.\n"
|
|
||||||
" \n"
|
|
||||||
" This rectangle must\n"
|
|
||||||
" remains a rectangle\n"
|
|
||||||
" because it is drawn\n"
|
|
||||||
" in lines having\n"
|
|
||||||
" lengths lower than\n"
|
|
||||||
" the specified number\n"
|
|
||||||
" of columns:\n"
|
|
||||||
" +----+\n"
|
|
||||||
" | |\n"
|
|
||||||
" +----+\n";
|
|
||||||
|
|
||||||
char *formatted = sc_str_wrap_lines(s, 24, 4);
|
|
||||||
assert(formatted);
|
|
||||||
|
|
||||||
assert(!strcmp(formatted, expected));
|
|
||||||
|
|
||||||
free(formatted);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_truncate(void) {
|
|
||||||
char s[] = "hello\nworld\n!";
|
|
||||||
size_t line_len = sc_str_truncate(s, sizeof(s), "\n");
|
|
||||||
|
|
||||||
assert(line_len == 5);
|
|
||||||
assert(!strcmp("hello", s));
|
|
||||||
|
|
||||||
char s2[] = "hello\r\nworkd\r\n!";
|
|
||||||
line_len = sc_str_truncate(s2, sizeof(s2), "\n\r");
|
|
||||||
|
|
||||||
assert(line_len == 5);
|
|
||||||
assert(!strcmp("hello", s));
|
|
||||||
|
|
||||||
char s3[] = "hello world\n!";
|
|
||||||
line_len = sc_str_truncate(s3, sizeof(s3), " \n\r");
|
|
||||||
|
|
||||||
assert(line_len == 5);
|
|
||||||
assert(!strcmp("hello", s3));
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
(void) argc;
|
|
||||||
(void) argv;
|
|
||||||
|
|
||||||
test_strncpy_simple();
|
|
||||||
test_strncpy_just_fit();
|
|
||||||
test_strncpy_truncated();
|
|
||||||
test_join_simple();
|
|
||||||
test_join_just_fit();
|
|
||||||
test_join_truncated_in_token();
|
|
||||||
test_join_truncated_before_sep();
|
|
||||||
test_join_truncated_after_sep();
|
|
||||||
test_quote();
|
|
||||||
test_utf8_truncate();
|
|
||||||
test_parse_integer();
|
|
||||||
test_parse_integers();
|
|
||||||
test_parse_integer_with_suffix();
|
|
||||||
test_strlist_contains();
|
|
||||||
test_wrap_lines();
|
|
||||||
test_truncate();
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "util/strbuf.h"
|
|
||||||
|
|
||||||
static void test_strbuf_simple(void) {
|
|
||||||
struct sc_strbuf buf;
|
|
||||||
bool ok = sc_strbuf_init(&buf, 10);
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
ok = sc_strbuf_append_staticstr(&buf, "Hello");
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
ok = sc_strbuf_append_char(&buf, ' ');
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
ok = sc_strbuf_append_staticstr(&buf, "world");
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
ok = sc_strbuf_append_staticstr(&buf, "!\n");
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
ok = sc_strbuf_append_staticstr(&buf, "This is a test");
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
ok = sc_strbuf_append_n(&buf, '.', 3);
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
assert(!strcmp(buf.s, "Hello world!\nThis is a test..."));
|
|
||||||
|
|
||||||
sc_strbuf_shrink(&buf);
|
|
||||||
assert(buf.len == buf.cap);
|
|
||||||
assert(!strcmp(buf.s, "Hello world!\nThis is a test..."));
|
|
||||||
|
|
||||||
free(buf.s);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
(void) argc;
|
|
||||||
(void) argv;
|
|
||||||
|
|
||||||
test_strbuf_simple();
|
|
||||||
return 0;
|
|
||||||
}
|
|
321
app/tests/test_strutil.c
Normal file
321
app/tests/test_strutil.c
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "util/str_util.h"
|
||||||
|
|
||||||
|
static void test_xstrncpy_simple(void) {
|
||||||
|
char s[] = "xxxxxxxxxx";
|
||||||
|
size_t w = xstrncpy(s, "abcdef", sizeof(s));
|
||||||
|
|
||||||
|
// returns strlen of copied string
|
||||||
|
assert(w == 6);
|
||||||
|
|
||||||
|
// is nul-terminated
|
||||||
|
assert(s[6] == '\0');
|
||||||
|
|
||||||
|
// does not write useless bytes
|
||||||
|
assert(s[7] == 'x');
|
||||||
|
|
||||||
|
// copies the content as expected
|
||||||
|
assert(!strcmp("abcdef", s));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_xstrncpy_just_fit(void) {
|
||||||
|
char s[] = "xxxxxx";
|
||||||
|
size_t w = xstrncpy(s, "abcdef", sizeof(s));
|
||||||
|
|
||||||
|
// returns strlen of copied string
|
||||||
|
assert(w == 6);
|
||||||
|
|
||||||
|
// is nul-terminated
|
||||||
|
assert(s[6] == '\0');
|
||||||
|
|
||||||
|
// copies the content as expected
|
||||||
|
assert(!strcmp("abcdef", s));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_xstrncpy_truncated(void) {
|
||||||
|
char s[] = "xxx";
|
||||||
|
size_t w = xstrncpy(s, "abcdef", sizeof(s));
|
||||||
|
|
||||||
|
// returns 'n' (sizeof(s))
|
||||||
|
assert(w == 4);
|
||||||
|
|
||||||
|
// is nul-terminated
|
||||||
|
assert(s[3] == '\0');
|
||||||
|
|
||||||
|
// copies the content as expected
|
||||||
|
assert(!strncmp("abcdef", s, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_xstrjoin_simple(void) {
|
||||||
|
const char *const tokens[] = { "abc", "de", "fghi", NULL };
|
||||||
|
char s[] = "xxxxxxxxxxxxxx";
|
||||||
|
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
|
||||||
|
|
||||||
|
// returns strlen of concatenation
|
||||||
|
assert(w == 11);
|
||||||
|
|
||||||
|
// is nul-terminated
|
||||||
|
assert(s[11] == '\0');
|
||||||
|
|
||||||
|
// does not write useless bytes
|
||||||
|
assert(s[12] == 'x');
|
||||||
|
|
||||||
|
// copies the content as expected
|
||||||
|
assert(!strcmp("abc de fghi", s));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_xstrjoin_just_fit(void) {
|
||||||
|
const char *const tokens[] = { "abc", "de", "fghi", NULL };
|
||||||
|
char s[] = "xxxxxxxxxxx";
|
||||||
|
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
|
||||||
|
|
||||||
|
// returns strlen of concatenation
|
||||||
|
assert(w == 11);
|
||||||
|
|
||||||
|
// is nul-terminated
|
||||||
|
assert(s[11] == '\0');
|
||||||
|
|
||||||
|
// copies the content as expected
|
||||||
|
assert(!strcmp("abc de fghi", s));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_xstrjoin_truncated_in_token(void) {
|
||||||
|
const char *const tokens[] = { "abc", "de", "fghi", NULL };
|
||||||
|
char s[] = "xxxxx";
|
||||||
|
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
|
||||||
|
|
||||||
|
// returns 'n' (sizeof(s))
|
||||||
|
assert(w == 6);
|
||||||
|
|
||||||
|
// is nul-terminated
|
||||||
|
assert(s[5] == '\0');
|
||||||
|
|
||||||
|
// copies the content as expected
|
||||||
|
assert(!strcmp("abc d", s));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_xstrjoin_truncated_before_sep(void) {
|
||||||
|
const char *const tokens[] = { "abc", "de", "fghi", NULL };
|
||||||
|
char s[] = "xxxxxx";
|
||||||
|
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
|
||||||
|
|
||||||
|
// returns 'n' (sizeof(s))
|
||||||
|
assert(w == 7);
|
||||||
|
|
||||||
|
// is nul-terminated
|
||||||
|
assert(s[6] == '\0');
|
||||||
|
|
||||||
|
// copies the content as expected
|
||||||
|
assert(!strcmp("abc de", s));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_xstrjoin_truncated_after_sep(void) {
|
||||||
|
const char *const tokens[] = { "abc", "de", "fghi", NULL };
|
||||||
|
char s[] = "xxxxxxx";
|
||||||
|
size_t w = xstrjoin(s, tokens, ' ', sizeof(s));
|
||||||
|
|
||||||
|
// returns 'n' (sizeof(s))
|
||||||
|
assert(w == 8);
|
||||||
|
|
||||||
|
// is nul-terminated
|
||||||
|
assert(s[7] == '\0');
|
||||||
|
|
||||||
|
// copies the content as expected
|
||||||
|
assert(!strcmp("abc de ", s));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_strquote(void) {
|
||||||
|
const char *s = "abcde";
|
||||||
|
char *out = strquote(s);
|
||||||
|
|
||||||
|
// add '"' at the beginning and the end
|
||||||
|
assert(!strcmp("\"abcde\"", out));
|
||||||
|
|
||||||
|
free(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_utf8_truncate(void) {
|
||||||
|
const char *s = "aÉbÔc";
|
||||||
|
assert(strlen(s) == 7); // É and Ô are 2 bytes-wide
|
||||||
|
|
||||||
|
size_t count;
|
||||||
|
|
||||||
|
count = utf8_truncation_index(s, 1);
|
||||||
|
assert(count == 1);
|
||||||
|
|
||||||
|
count = utf8_truncation_index(s, 2);
|
||||||
|
assert(count == 1); // É is 2 bytes-wide
|
||||||
|
|
||||||
|
count = utf8_truncation_index(s, 3);
|
||||||
|
assert(count == 3);
|
||||||
|
|
||||||
|
count = utf8_truncation_index(s, 4);
|
||||||
|
assert(count == 4);
|
||||||
|
|
||||||
|
count = utf8_truncation_index(s, 5);
|
||||||
|
assert(count == 4); // Ô is 2 bytes-wide
|
||||||
|
|
||||||
|
count = utf8_truncation_index(s, 6);
|
||||||
|
assert(count == 6);
|
||||||
|
|
||||||
|
count = utf8_truncation_index(s, 7);
|
||||||
|
assert(count == 7);
|
||||||
|
|
||||||
|
count = utf8_truncation_index(s, 8);
|
||||||
|
assert(count == 7); // no more chars
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_parse_integer(void) {
|
||||||
|
long value;
|
||||||
|
bool ok = parse_integer("1234", &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == 1234);
|
||||||
|
|
||||||
|
ok = parse_integer("-1234", &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == -1234);
|
||||||
|
|
||||||
|
ok = parse_integer("1234k", &value);
|
||||||
|
assert(!ok);
|
||||||
|
|
||||||
|
ok = parse_integer("123456789876543212345678987654321", &value);
|
||||||
|
assert(!ok); // out-of-range
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_parse_integers(void) {
|
||||||
|
long values[5];
|
||||||
|
|
||||||
|
size_t count = parse_integers("1234", ':', 5, values);
|
||||||
|
assert(count == 1);
|
||||||
|
assert(values[0] == 1234);
|
||||||
|
|
||||||
|
count = parse_integers("1234:5678", ':', 5, values);
|
||||||
|
assert(count == 2);
|
||||||
|
assert(values[0] == 1234);
|
||||||
|
assert(values[1] == 5678);
|
||||||
|
|
||||||
|
count = parse_integers("1234:5678", ':', 2, values);
|
||||||
|
assert(count == 2);
|
||||||
|
assert(values[0] == 1234);
|
||||||
|
assert(values[1] == 5678);
|
||||||
|
|
||||||
|
count = parse_integers("1234:-5678", ':', 2, values);
|
||||||
|
assert(count == 2);
|
||||||
|
assert(values[0] == 1234);
|
||||||
|
assert(values[1] == -5678);
|
||||||
|
|
||||||
|
count = parse_integers("1:2:3:4:5", ':', 5, values);
|
||||||
|
assert(count == 5);
|
||||||
|
assert(values[0] == 1);
|
||||||
|
assert(values[1] == 2);
|
||||||
|
assert(values[2] == 3);
|
||||||
|
assert(values[3] == 4);
|
||||||
|
assert(values[4] == 5);
|
||||||
|
|
||||||
|
count = parse_integers("1234:5678", ':', 1, values);
|
||||||
|
assert(count == 0); // max_items == 1
|
||||||
|
|
||||||
|
count = parse_integers("1:2:3:4:5", ':', 3, values);
|
||||||
|
assert(count == 0); // max_items == 3
|
||||||
|
|
||||||
|
count = parse_integers(":1234", ':', 5, values);
|
||||||
|
assert(count == 0); // invalid
|
||||||
|
|
||||||
|
count = parse_integers("1234:", ':', 5, values);
|
||||||
|
assert(count == 0); // invalid
|
||||||
|
|
||||||
|
count = parse_integers("1234:", ':', 1, values);
|
||||||
|
assert(count == 0); // invalid, even when max_items == 1
|
||||||
|
|
||||||
|
count = parse_integers("1234::5678", ':', 5, values);
|
||||||
|
assert(count == 0); // invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_parse_integer_with_suffix(void) {
|
||||||
|
long value;
|
||||||
|
bool ok = parse_integer_with_suffix("1234", &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == 1234);
|
||||||
|
|
||||||
|
ok = parse_integer_with_suffix("-1234", &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == -1234);
|
||||||
|
|
||||||
|
ok = parse_integer_with_suffix("1234k", &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == 1234000);
|
||||||
|
|
||||||
|
ok = parse_integer_with_suffix("1234m", &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == 1234000000);
|
||||||
|
|
||||||
|
ok = parse_integer_with_suffix("-1234k", &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == -1234000);
|
||||||
|
|
||||||
|
ok = parse_integer_with_suffix("-1234m", &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == -1234000000);
|
||||||
|
|
||||||
|
ok = parse_integer_with_suffix("123456789876543212345678987654321", &value);
|
||||||
|
assert(!ok); // out-of-range
|
||||||
|
|
||||||
|
char buf[32];
|
||||||
|
|
||||||
|
sprintf(buf, "%ldk", LONG_MAX / 2000);
|
||||||
|
ok = parse_integer_with_suffix(buf, &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == LONG_MAX / 2000 * 1000);
|
||||||
|
|
||||||
|
sprintf(buf, "%ldm", LONG_MAX / 2000);
|
||||||
|
ok = parse_integer_with_suffix(buf, &value);
|
||||||
|
assert(!ok);
|
||||||
|
|
||||||
|
sprintf(buf, "%ldk", LONG_MIN / 2000);
|
||||||
|
ok = parse_integer_with_suffix(buf, &value);
|
||||||
|
assert(ok);
|
||||||
|
assert(value == LONG_MIN / 2000 * 1000);
|
||||||
|
|
||||||
|
sprintf(buf, "%ldm", LONG_MIN / 2000);
|
||||||
|
ok = parse_integer_with_suffix(buf, &value);
|
||||||
|
assert(!ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_strlist_contains(void) {
|
||||||
|
assert(strlist_contains("a,bc,def", ',', "bc"));
|
||||||
|
assert(!strlist_contains("a,bc,def", ',', "b"));
|
||||||
|
assert(strlist_contains("", ',', ""));
|
||||||
|
assert(strlist_contains("abc,", ',', ""));
|
||||||
|
assert(strlist_contains(",abc", ',', ""));
|
||||||
|
assert(strlist_contains("abc,,def", ',', ""));
|
||||||
|
assert(!strlist_contains("abc", ',', ""));
|
||||||
|
assert(strlist_contains(",,|x", '|', ",,"));
|
||||||
|
assert(strlist_contains("xyz", '\0', "xyz"));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
|
test_xstrncpy_simple();
|
||||||
|
test_xstrncpy_just_fit();
|
||||||
|
test_xstrncpy_truncated();
|
||||||
|
test_xstrjoin_simple();
|
||||||
|
test_xstrjoin_just_fit();
|
||||||
|
test_xstrjoin_truncated_in_token();
|
||||||
|
test_xstrjoin_truncated_before_sep();
|
||||||
|
test_xstrjoin_truncated_after_sep();
|
||||||
|
test_strquote();
|
||||||
|
test_utf8_truncate();
|
||||||
|
test_parse_integer();
|
||||||
|
test_parse_integers();
|
||||||
|
test_parse_integer_with_suffix();
|
||||||
|
test_strlist_contains();
|
||||||
|
return 0;
|
||||||
|
}
|
@ -7,7 +7,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.0.2'
|
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
@ -2,7 +2,7 @@ apply plugin: 'checkstyle'
|
|||||||
check.dependsOn 'checkstyle'
|
check.dependsOn 'checkstyle'
|
||||||
|
|
||||||
checkstyle {
|
checkstyle {
|
||||||
toolVersion = '9.0.1'
|
toolVersion = '6.19'
|
||||||
}
|
}
|
||||||
|
|
||||||
task checkstyle(type: Checkstyle) {
|
task checkstyle(type: Checkstyle) {
|
||||||
|
@ -37,14 +37,6 @@ page at http://checkstyle.sourceforge.net/config.html -->
|
|||||||
|
|
||||||
<module name="SuppressWarningsFilter"/>
|
<module name="SuppressWarningsFilter"/>
|
||||||
|
|
||||||
<module name="LineLength">
|
|
||||||
<!-- what is a good max value? -->
|
|
||||||
<property name="max" value="150" />
|
|
||||||
<!-- ignore lines like "$File: //depot/... $" -->
|
|
||||||
<property name="ignorePattern" value="\$File.*\$" />
|
|
||||||
<property name="severity" value="info" />
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="TreeWalker">
|
<module name="TreeWalker">
|
||||||
|
|
||||||
<!-- Checks for Naming Conventions. -->
|
<!-- Checks for Naming Conventions. -->
|
||||||
@ -80,6 +72,13 @@ page at http://checkstyle.sourceforge.net/config.html -->
|
|||||||
|
|
||||||
<!-- Checks for Size Violations. -->
|
<!-- Checks for Size Violations. -->
|
||||||
<!-- See http://checkstyle.sf.net/config_sizes.html -->
|
<!-- See http://checkstyle.sf.net/config_sizes.html -->
|
||||||
|
<module name="LineLength">
|
||||||
|
<!-- what is a good max value? -->
|
||||||
|
<property name="max" value="150" />
|
||||||
|
<!-- ignore lines like "$File: //depot/... $" -->
|
||||||
|
<property name="ignorePattern" value="\$File.*\$" />
|
||||||
|
<property name="severity" value="info" />
|
||||||
|
</module>
|
||||||
<module name="MethodLength" />
|
<module name="MethodLength" />
|
||||||
<module name="ParameterNumber">
|
<module name="ParameterNumber">
|
||||||
<property name="ignoreOverriddenMethods" value="true"/>
|
<property name="ignoreOverriddenMethods" value="true"/>
|
||||||
@ -153,6 +152,26 @@ page at http://checkstyle.sourceforge.net/config.html -->
|
|||||||
</module>
|
</module>
|
||||||
<module name="UpperEll" />
|
<module name="UpperEll" />
|
||||||
|
|
||||||
|
<module name="FileContentsHolder" />
|
||||||
|
<!-- Required by comment suppression filters -->
|
||||||
|
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="SuppressionFilter">
|
||||||
|
<!--<property name="file" value="team-props/checkstyle/checkstyle-suppressions.xml" />-->
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- Enable suppression comments -->
|
||||||
|
<module name="SuppressionCommentFilter">
|
||||||
|
<property name="offCommentFormat" value="CHECKSTYLE IGNORE\s+(\S+)" />
|
||||||
|
<property name="onCommentFormat" value="CHECKSTYLE END IGNORE\s+(\S+)" />
|
||||||
|
<property name="checkFormat" value="$1" />
|
||||||
|
</module>
|
||||||
|
<module name="SuppressWithNearbyCommentFilter">
|
||||||
|
<!-- Syntax is "SUPPRESS CHECKSTYLE name" -->
|
||||||
|
<property name="commentFormat" value="SUPPRESS CHECKSTYLE (\w+)" />
|
||||||
|
<property name="checkFormat" value="$1" />
|
||||||
|
<property name="influenceFormat" value="1" />
|
||||||
</module>
|
</module>
|
||||||
|
|
||||||
</module>
|
</module>
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@ -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,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',
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 31
|
compileSdkVersion 30
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.genymobile.scrcpy"
|
applicationId "com.genymobile.scrcpy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 31
|
targetSdkVersion 30
|
||||||
versionCode 12000
|
versionCode 11900
|
||||||
versionName "1.20"
|
versionName "1.19"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@ -20,7 +20,7 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
testImplementation 'junit:junit:4.13.1'
|
testImplementation 'junit:junit:4.13'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$project.rootDir/config/android-checkstyle.gradle"
|
apply from: "$project.rootDir/config/android-checkstyle.gradle"
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package com.genymobile.scrcpy;
|
|||||||
|
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user