Compare commits
176 Commits
install_re
...
v1.18
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60c4e886d4 | ||
|
|
ff7baad709 | ||
|
|
e712f30ceb | ||
|
|
a9d9cbf8b5 | ||
|
|
fda32928c1 | ||
|
|
710e80aa0d | ||
|
|
77e96d745b | ||
|
|
1c95043478 | ||
|
|
488991116b | ||
|
|
5c95d18beb | ||
|
|
1039f9b531 | ||
|
|
19ca02cd8f | ||
|
|
937fa704a6 | ||
|
|
7db0189f23 | ||
|
|
8b90e1d3f4 | ||
|
|
df017160ed | ||
|
|
b846d3a085 | ||
|
|
a1f2094787 | ||
|
|
9b89b7ab72 | ||
|
|
7343b233e4 | ||
|
|
cd2894570d | ||
|
|
4af317d40d | ||
|
|
318b6a572e | ||
|
|
e8b053ad2f | ||
|
|
a5d71eee45 | ||
|
|
af228706f1 | ||
|
|
f76fe2c0d4 | ||
|
|
16a63e0917 | ||
|
|
f7533e8896 | ||
|
|
969bfd4374 | ||
|
|
506f918fb7 | ||
|
|
6fd7e89da1 | ||
|
|
4c31911df2 | ||
|
|
d3d955f67b | ||
|
|
8121b0b7e7 | ||
|
|
2e9d520080 | ||
|
|
1b7c9b3e1c | ||
|
|
9bed8cf3f4 | ||
|
|
6adc97198b | ||
|
|
6a2cd089a1 | ||
|
|
dcee7c0f7f | ||
|
|
e604e8a752 | ||
|
|
f19c455110 | ||
|
|
83116fc199 | ||
|
|
72081a241b | ||
|
|
42b3d1e66e | ||
|
|
1e64f0f931 | ||
|
|
8fb5715740 | ||
|
|
f062dfd30b | ||
|
|
644a5ef14a | ||
|
|
1b9dcce23c | ||
|
|
233f8e6cc4 | ||
|
|
9a7d351d67 | ||
|
|
d00ee640c0 | ||
|
|
ae6ec7a194 | ||
|
|
84f17fdeab | ||
|
|
1cde68a1fa | ||
|
|
45e7280148 | ||
|
|
41a0383d7c | ||
|
|
d39161f753 | ||
|
|
5af9d0ee0f | ||
|
|
fd0dc6c0cd | ||
|
|
151bc16644 | ||
|
|
ffc00210e9 | ||
|
|
243854a408 | ||
|
|
8b90dc61b9 | ||
|
|
2a5dfc1c17 | ||
|
|
e3fccc5a5e | ||
|
|
0541f1bff2 | ||
|
|
0272e6dc77 | ||
|
|
2a94a2b119 | ||
|
|
e91acdb0c4 | ||
|
|
6f5ad21f57 | ||
|
|
08b3086ffc | ||
|
|
deab7da761 | ||
|
|
f7a1b67d66 | ||
|
|
cbed38799e | ||
|
|
5beb7d6c02 | ||
|
|
5980183a33 | ||
|
|
fe8de893ca | ||
|
|
a974483c15 | ||
|
|
1b072a24c4 | ||
|
|
08f1fd46c8 | ||
|
|
2ddf760c09 | ||
|
|
5d9e96dc4e | ||
|
|
de9b79ec2d | ||
|
|
55806e7d31 | ||
|
|
21b590b766 | ||
|
|
d7e6589677 | ||
|
|
b4ee9f27ce | ||
|
|
6fa63cf6f8 | ||
|
|
50eecdab28 | ||
|
|
9576283907 | ||
|
|
66c581851f | ||
|
|
bb4614d558 | ||
|
|
aaf7875d92 | ||
|
|
b9c3f65fd8 | ||
|
|
d0739911a3 | ||
|
|
498ad23e98 | ||
|
|
964b6d2243 | ||
|
|
8cc057c8f1 | ||
|
|
edee69d637 | ||
|
|
8ef4c044fa | ||
|
|
c23c38f99d | ||
|
|
65c4f487b3 | ||
|
|
c6d7f5ee96 | ||
|
|
28f6cbaea6 | ||
|
|
08fc6694e1 | ||
|
|
d0983db592 | ||
|
|
fb7870500a | ||
|
|
33006561c7 | ||
|
|
a09733d175 | ||
|
|
07a85b7c94 | ||
|
|
6231f683af | ||
|
|
9826c5c4a4 | ||
|
|
2812de8a9a | ||
|
|
d50c678a5f | ||
|
|
b77932a5b7 | ||
|
|
1d615a0d51 | ||
|
|
fb0bcaebc2 | ||
|
|
dd453ad041 | ||
|
|
0308ef43f2 | ||
|
|
40febf4a91 | ||
|
|
429fdef04f | ||
|
|
d1789f082a | ||
|
|
eb7e1070cf | ||
|
|
386f017ba9 | ||
|
|
cc48b24324 | ||
|
|
597c54f049 | ||
|
|
955da3b578 | ||
|
|
cb9c42bdcb | ||
|
|
fb9f9848bd | ||
|
|
c50b958ee4 | ||
|
|
441d3fb119 | ||
|
|
cb197ee3a2 | ||
|
|
218636dc10 | ||
|
|
b16b65a715 | ||
|
|
a3aa5ac95e | ||
|
|
0207e3df33 | ||
|
|
9cd1a7380d | ||
|
|
24b637b972 | ||
|
|
76a3d9805b | ||
|
|
50b4a730e3 | ||
|
|
ea2369f568 | ||
|
|
0538e9645b | ||
|
|
626094ad13 | ||
|
|
a566635c43 | ||
|
|
862948b132 | ||
|
|
c0c4ba7009 | ||
|
|
c53bd4d8b6 | ||
|
|
54f5c42d7b | ||
|
|
21d206f360 | ||
|
|
d2689fc168 | ||
|
|
f6320c7e31 | ||
|
|
30e619d37f | ||
|
|
c0dde0fade | ||
|
|
ace438e52a | ||
|
|
8e83f3e8af | ||
|
|
97b001e7c0 | ||
|
|
d8e9ad20b0 | ||
|
|
b566700bfd | ||
|
|
7afd149f4b | ||
|
|
6a50231698 | ||
|
|
b8edcf52b0 | ||
|
|
94eff0a4bb | ||
|
|
8dbb1676b7 | ||
|
|
ab912c23e7 | ||
|
|
59feb2a15c | ||
|
|
6385b8c162 | ||
|
|
037be4af21 | ||
|
|
1e215199dc | ||
|
|
d580ee30f1 | ||
|
|
821c175730 | ||
|
|
cc6f5020d8 | ||
|
|
4bd9da4c93 | ||
|
|
aa8b571389 |
31
BUILD.md
31
BUILD.md
@@ -13,8 +13,8 @@ First, you need to install the required packages:
|
|||||||
```bash
|
```bash
|
||||||
# for Debian/Ubuntu
|
# for Debian/Ubuntu
|
||||||
sudo apt install ffmpeg libsdl2-2.0-0 adb wget \
|
sudo apt install ffmpeg libsdl2-2.0-0 adb wget \
|
||||||
gcc git pkg-config meson ninja-build \
|
gcc git pkg-config meson ninja-build libsdl2-dev \
|
||||||
libavcodec-dev libavformat-dev libavutil-dev libsdl2-dev
|
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Then clone the repo and execute the installation script
|
Then clone the repo and execute the installation script
|
||||||
@@ -33,6 +33,12 @@ git pull
|
|||||||
./install_release.sh
|
./install_release.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To uninstall:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ninja -Cbuild-auto uninstall
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Branches
|
## Branches
|
||||||
|
|
||||||
@@ -85,8 +91,8 @@ Install the required packages from your package manager.
|
|||||||
sudo apt install ffmpeg libsdl2-2.0-0 adb
|
sudo apt install ffmpeg libsdl2-2.0-0 adb
|
||||||
|
|
||||||
# client build dependencies
|
# client build dependencies
|
||||||
sudo apt install gcc git pkg-config meson ninja-build \
|
sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \
|
||||||
libavcodec-dev libavformat-dev libavutil-dev libsdl2-dev
|
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev
|
||||||
|
|
||||||
# server build dependencies
|
# server build dependencies
|
||||||
sudo apt install openjdk-11-jdk
|
sudo apt install openjdk-11-jdk
|
||||||
@@ -198,12 +204,12 @@ brew install pkg-config meson
|
|||||||
```
|
```
|
||||||
|
|
||||||
Additionally, if you want to build the server, install Java 8 from Caskroom, and
|
Additionally, if you want to build the server, install Java 8 from Caskroom, and
|
||||||
make it avaliable from the `PATH`:
|
make it available from the `PATH`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew tap homebrew/cask-versions
|
brew tap homebrew/cask-versions
|
||||||
brew cask install adoptopenjdk/openjdk/adoptopenjdk8
|
brew install adoptopenjdk/openjdk/adoptopenjdk11
|
||||||
export JAVA_HOME="$(/usr/libexec/java_home --version 1.8)"
|
export JAVA_HOME="$(/usr/libexec/java_home --version 1.11)"
|
||||||
export PATH="$JAVA_HOME/bin:$PATH"
|
export PATH="$JAVA_HOME/bin:$PATH"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -295,11 +301,16 @@ After a successful build, you can install _scrcpy_ on the system:
|
|||||||
sudo ninja -Cx install # without sudo on Windows
|
sudo ninja -Cx install # without sudo on Windows
|
||||||
```
|
```
|
||||||
|
|
||||||
This installs two files:
|
This installs three files:
|
||||||
|
|
||||||
- `/usr/local/bin/scrcpy`
|
- `/usr/local/bin/scrcpy`
|
||||||
- `/usr/local/share/scrcpy/scrcpy-server`
|
- `/usr/local/share/scrcpy/scrcpy-server`
|
||||||
|
- `/usr/local/share/man/man1/scrcpy.1`
|
||||||
Just remove them to "uninstall" the application.
|
|
||||||
|
|
||||||
You can then [run](README.md#run) _scrcpy_.
|
You can then [run](README.md#run) _scrcpy_.
|
||||||
|
|
||||||
|
### Uninstall
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ninja -Cx uninstall # without sudo on Windows
|
||||||
|
```
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ There are two [frames][video_buffer] simultaneously in memory:
|
|||||||
- the **rendering** frame, rendered in a texture from the main thread.
|
- the **rendering** frame, rendered in a texture from the main thread.
|
||||||
|
|
||||||
When a new decoded frame is available, the decoder _swaps_ the decoding and
|
When a new decoded frame is available, the decoder _swaps_ the decoding and
|
||||||
rendering frame (with proper synchronization). Thus, it immediatly starts
|
rendering frame (with proper synchronization). Thus, it immediately starts
|
||||||
to decode a new frame while the main thread renders the last one.
|
to decode a new frame while the main thread renders the last one.
|
||||||
|
|
||||||
If a [recorder] is present (i.e. `--record` is enabled), then it muxes the raw
|
If a [recorder] is present (i.e. `--record` is enabled), then it muxes the raw
|
||||||
|
|||||||
217
FAQ.it.md
Normal file
217
FAQ.it.md
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
_Apri le [FAQ](FAQ.md) originali e sempre aggiornate._
|
||||||
|
|
||||||
|
# Domande Frequenti (FAQ)
|
||||||
|
|
||||||
|
Questi sono i problemi più comuni riportati e i loro stati.
|
||||||
|
|
||||||
|
|
||||||
|
## Problemi di `adb`
|
||||||
|
|
||||||
|
`scrcpy` esegue comandi `adb` per inizializzare la connessione con il dispositivo. Se `adb` fallisce, scrcpy non funzionerà.
|
||||||
|
|
||||||
|
In questo caso sarà stampato questo errore:
|
||||||
|
|
||||||
|
> ERROR: "adb push" returned with value 1
|
||||||
|
|
||||||
|
Questo solitamente non è un bug di _scrcpy_, ma un problema del tuo ambiente.
|
||||||
|
|
||||||
|
Per trovare la causa, esegui:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb devices
|
||||||
|
```
|
||||||
|
|
||||||
|
### `adb` not found (`adb` non trovato)
|
||||||
|
|
||||||
|
È necessario che `adb` sia accessibile dal tuo `PATH`.
|
||||||
|
|
||||||
|
In Windows, la cartella corrente è nel tuo `PATH` e `adb.exe` è incluso nella release, perciò dovrebbe già essere pronto all'uso.
|
||||||
|
|
||||||
|
|
||||||
|
### Device unauthorized (Dispositivo non autorizzato)
|
||||||
|
|
||||||
|
Controlla [stackoverflow][device-unauthorized] (in inglese).
|
||||||
|
|
||||||
|
[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
|
||||||
|
|
||||||
|
|
||||||
|
### Device not detected (Dispositivo non rilevato)
|
||||||
|
|
||||||
|
> adb: error: failed to get feature set: no devices/emulators found
|
||||||
|
|
||||||
|
Controlla di aver abilitato correttamente il [debug con adb][enable-adb] (link in inglese).
|
||||||
|
|
||||||
|
Se il tuo dispositivo non è rilevato, potresti avere bisogno dei [driver][drivers] (link in inglese) (in Windows).
|
||||||
|
|
||||||
|
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
|
||||||
|
[drivers]: https://developer.android.com/studio/run/oem-usb.html
|
||||||
|
|
||||||
|
|
||||||
|
### Più dispositivi connessi
|
||||||
|
|
||||||
|
Se più dispositivi sono connessi, riscontrerai questo errore:
|
||||||
|
|
||||||
|
> adb: error: failed to get feature set: more than one device/emulator
|
||||||
|
|
||||||
|
l'identificatore del tuo dispositivo deve essere fornito:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy -s 01234567890abcdef
|
||||||
|
```
|
||||||
|
|
||||||
|
Notare che se il tuo dispositivo è connesso mediante TCP/IP, riscontrerai questo messaggio:
|
||||||
|
|
||||||
|
> adb: error: more than one device/emulator
|
||||||
|
> ERROR: "adb reverse" returned with value 1
|
||||||
|
> WARN: 'adb reverse' failed, fallback to 'adb forward'
|
||||||
|
|
||||||
|
Questo è un problema atteso (a causa di un bug di una vecchia versione di Android, vedi [#5] (link in inglese)), ma in quel caso scrcpy ripiega su un metodo differente, il quale dovrebbe funzionare.
|
||||||
|
|
||||||
|
[#5]: https://github.com/Genymobile/scrcpy/issues/5
|
||||||
|
|
||||||
|
|
||||||
|
### Conflitti tra versioni di adb
|
||||||
|
|
||||||
|
> adb server version (41) doesn't match this client (39); killing...
|
||||||
|
|
||||||
|
L'errore compare quando usi più versioni di `adb` simultaneamente. Devi trovare il programma che sta utilizzando una versione differente di `adb` e utilizzare la stessa versione dappertutto.
|
||||||
|
|
||||||
|
Puoi sovrascrivere i binari di `adb` nell'altro programma, oppure chiedere a _scrcpy_ di usare un binario specifico di `adb`, impostando la variabile d'ambiente `ADB`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
set ADB=/path/to/your/adb
|
||||||
|
scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Device disconnected (Dispositivo disconnesso)
|
||||||
|
|
||||||
|
Se _scrcpy_ si interrompe con l'avviso "Device disconnected", allora la connessione `adb` è stata chiusa.
|
||||||
|
|
||||||
|
Prova con un altro cavo USB o inseriscilo in un'altra porta USB. Vedi [#281] (in inglese) e [#283] (in inglese).
|
||||||
|
|
||||||
|
[#281]: https://github.com/Genymobile/scrcpy/issues/281
|
||||||
|
[#283]: https://github.com/Genymobile/scrcpy/issues/283
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Problemi di controllo
|
||||||
|
|
||||||
|
### Mouse e tastiera non funzionano
|
||||||
|
|
||||||
|
Su alcuni dispositivi potresti dover abilitare un opzione che permette l'[input simulato][simulating input] (link in inglese). Nelle opzioni sviluppatore, abilita:
|
||||||
|
|
||||||
|
> **Debug USB (Impostazioni di sicurezza)**
|
||||||
|
> _Permetti la concessione dei permessi e la simulazione degli input mediante il debug USB_
|
||||||
|
<!--- Ho tradotto personalmente il testo sopra, non conosco esattamente il testo reale --->
|
||||||
|
|
||||||
|
[simulating input]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
|
||||||
|
|
||||||
|
|
||||||
|
### I caratteri speciali non funzionano
|
||||||
|
|
||||||
|
Iniettare del testo in input è [limitato ai caratteri ASCII][text-input] (link in inglese). Un trucco permette di iniettare dei [caratteri accentati][accented-characters] (link in inglese), ma questo è tutto. Vedi [#37] (link in inglese).
|
||||||
|
|
||||||
|
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
|
||||||
|
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
|
||||||
|
[#37]: https://github.com/Genymobile/scrcpy/issues/37
|
||||||
|
|
||||||
|
|
||||||
|
## Problemi del client
|
||||||
|
|
||||||
|
### La qualità è bassa
|
||||||
|
|
||||||
|
Se la definizione della finestra del tuo client è minore di quella del tuo dispositivo, allora potresti avere una bassa qualità di visualizzazione, specialmente individuabile nei testi (vedi [#40] (link in inglese)).
|
||||||
|
|
||||||
|
[#40]: https://github.com/Genymobile/scrcpy/issues/40
|
||||||
|
|
||||||
|
Per migliorare la qualità di ridimensionamento (downscaling), il filtro trilineare è applicato automaticamente se il renderizzatore è OpenGL e se supporta la creazione di mipmap.
|
||||||
|
|
||||||
|
In Windows, potresti voler forzare OpenGL:
|
||||||
|
|
||||||
|
```
|
||||||
|
scrcpy --render-driver=opengl
|
||||||
|
```
|
||||||
|
|
||||||
|
Potresti anche dover configurare il [comportamento di ridimensionamento][scaling behavior] (link in inglese):
|
||||||
|
|
||||||
|
> `scrcpy.exe` > Propietà > Compatibilità > Modifica impostazioni DPI elevati > Esegui l'override del comportamento di ridimensionamento DPI elevati > Ridimensionamento eseguito per: _Applicazione_.
|
||||||
|
|
||||||
|
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Crash del compositore KWin
|
||||||
|
|
||||||
|
In Plasma Desktop, il compositore è disabilitato mentre _scrcpy_ è in esecuzione.
|
||||||
|
|
||||||
|
Come soluzione alternativa, [disattiva la "composizione dei blocchi"][kwin] (link in inglese).
|
||||||
|
<!--- Non sono sicuro di aver tradotto correttamente la stringa di testo del pulsante --->
|
||||||
|
|
||||||
|
[kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613
|
||||||
|
|
||||||
|
|
||||||
|
## Crash
|
||||||
|
|
||||||
|
### Eccezione
|
||||||
|
|
||||||
|
Ci potrebbero essere molte ragioni. Una causa comune è che il codificatore hardware del tuo dispositivo non riesce a codificare alla definizione selezionata:
|
||||||
|
|
||||||
|
> ```
|
||||||
|
> ERROR: Exception on thread Thread[main,5,main]
|
||||||
|
> android.media.MediaCodec$CodecException: Error 0xfffffc0e
|
||||||
|
> ...
|
||||||
|
> Exit due to uncaughtException in main thread:
|
||||||
|
> ERROR: Could not open video stream
|
||||||
|
> INFO: Initial texture: 1080x2336
|
||||||
|
> ```
|
||||||
|
|
||||||
|
o
|
||||||
|
|
||||||
|
> ```
|
||||||
|
> ERROR: Exception on thread Thread[main,5,main]
|
||||||
|
> java.lang.IllegalStateException
|
||||||
|
> at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method)
|
||||||
|
> ```
|
||||||
|
|
||||||
|
Prova con una definizione inferiore:
|
||||||
|
|
||||||
|
```
|
||||||
|
scrcpy -m 1920
|
||||||
|
scrcpy -m 1024
|
||||||
|
scrcpy -m 800
|
||||||
|
```
|
||||||
|
|
||||||
|
Potresti anche provare un altro [codificatore](README.it.md#codificatore).
|
||||||
|
|
||||||
|
|
||||||
|
## Linea di comando in Windows
|
||||||
|
|
||||||
|
Alcuni utenti Windows non sono familiari con la riga di comando. Qui è descritto come aprire un terminale ed eseguire `scrcpy` con gli argomenti:
|
||||||
|
|
||||||
|
1. Premi <kbd>Windows</kbd>+<kbd>r</kbd>, questo apre una finestra di dialogo.
|
||||||
|
2. Scrivi `cmd` e premi <kbd>Enter</kbd>, questo apre un terminale.
|
||||||
|
3. Vai nella tua cartella di _scrcpy_ scrivendo (adatta il percorso):
|
||||||
|
|
||||||
|
```bat
|
||||||
|
cd C:\Users\user\Downloads\scrcpy-win64-xxx
|
||||||
|
```
|
||||||
|
|
||||||
|
e premi <kbd>Enter</kbd>
|
||||||
|
4. Scrivi il tuo comando. Per esempio:
|
||||||
|
|
||||||
|
```bat
|
||||||
|
scrcpy --record file.mkv
|
||||||
|
```
|
||||||
|
|
||||||
|
Se pianifichi di utilizzare sempre gli stessi argomenti, crea un file `myscrcpy.bat` (abilita mostra [estensioni nomi file][show file extensions] per evitare di far confusione) contenente il tuo comando nella cartella di `scrcpy`. Per esempio:
|
||||||
|
|
||||||
|
```bat
|
||||||
|
scrcpy --prefer-text --turn-screen-off --stay-awake
|
||||||
|
```
|
||||||
|
|
||||||
|
Poi fai doppio click su quel file.
|
||||||
|
|
||||||
|
Potresti anche modificare (una copia di) `scrcpy-console.bat` o `scrcpy-noconsole.vbs` per aggiungere alcuni argomenti.
|
||||||
|
|
||||||
|
[show file extensions]: https://www.techpedia.it/14-windows/windows-10/171-visualizzare-le-estensioni-nomi-file-con-windows-10
|
||||||
10
FAQ.md
10
FAQ.md
@@ -1,5 +1,7 @@
|
|||||||
# Frequently Asked Questions
|
# Frequently Asked Questions
|
||||||
|
|
||||||
|
[Read in another language](#translations)
|
||||||
|
|
||||||
Here are the common reported problems and their status.
|
Here are the common reported problems and their status.
|
||||||
|
|
||||||
|
|
||||||
@@ -230,3 +232,11 @@ You could also edit (a copy of) `scrcpy-console.bat` or `scrcpy-noconsole.vbs`
|
|||||||
to add some arguments.
|
to add some arguments.
|
||||||
|
|
||||||
[show file extensions]: https://www.howtogeek.com/205086/beginner-how-to-make-windows-show-file-extensions/
|
[show file extensions]: https://www.howtogeek.com/205086/beginner-how-to-make-windows-show-file-extensions/
|
||||||
|
|
||||||
|
|
||||||
|
## Translations
|
||||||
|
|
||||||
|
This FAQ is available in other languages:
|
||||||
|
|
||||||
|
- [Italiano (Italiano, `it`) - v1.17](FAQ.it.md)
|
||||||
|
- [한국어 (Korean, `ko`) - v1.11](FAQ.ko.md)
|
||||||
|
|||||||
742
README.it.md
Normal file
742
README.it.md
Normal file
@@ -0,0 +1,742 @@
|
|||||||
|
_Apri il [README](README.md) originale e sempre aggiornato._
|
||||||
|
|
||||||
|
# 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_.
|
||||||
|
Funziona su _GNU/Linux_, _Windows_ e _macOS_.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Si concentra su:
|
||||||
|
|
||||||
|
- **leggerezza** (nativo, mostra solo lo schermo del dispositivo)
|
||||||
|
- **prestazioni** (30~60fps)
|
||||||
|
- **qualità** (1920×1080 o superiore)
|
||||||
|
- **bassa latenza** ([35~70ms][lowlatency])
|
||||||
|
- **tempo di avvio basso** (~ 1secondo per visualizzare la prima immagine)
|
||||||
|
- **non invadenza** (nulla viene lasciato installato sul dispositivo)
|
||||||
|
|
||||||
|
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
|
||||||
|
|
||||||
|
|
||||||
|
## Requisiti
|
||||||
|
|
||||||
|
Il dispositivo Android richiede almeno le API 21 (Android 5.0).
|
||||||
|
|
||||||
|
Assiucurati di aver [attivato il debug usb][enable-adb] sul(/i) tuo(i) dispositivo(/i).
|
||||||
|
|
||||||
|
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
|
||||||
|
|
||||||
|
In alcuni dispositivi, devi anche abilitare [un'opzione aggiuntiva][control] per controllarli con tastiera e mouse.
|
||||||
|
|
||||||
|
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
|
||||||
|
|
||||||
|
## Ottieni l'app
|
||||||
|
|
||||||
|
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
|
||||||
|
|
||||||
|
### Sommario
|
||||||
|
|
||||||
|
- Linux: `apt install scrcpy`
|
||||||
|
- Windows: [download](README.md#windows)
|
||||||
|
- macOS: `brew install scrcpy`
|
||||||
|
|
||||||
|
Compila dai sorgenti: [BUILD] (in inglese) ([procedimento semplificato][BUILD_simple] (in inglese))
|
||||||
|
|
||||||
|
[BUILD]: BUILD.md
|
||||||
|
[BUILD_simple]: BUILD.md#simple
|
||||||
|
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
Su Debian (_testing_ e _sid_ per ora) e Ubuntu (20.04):
|
||||||
|
|
||||||
|
```
|
||||||
|
apt install scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
È disponibile anche un pacchetto [Snap]: [`scrcpy`][snap-link].
|
||||||
|
|
||||||
|
[snap-link]: https://snapstats.org/snaps/scrcpy
|
||||||
|
|
||||||
|
[snap]: https://it.wikipedia.org/wiki/Snappy_(gestore_pacchetti)
|
||||||
|
|
||||||
|
Per Fedora, è disponibile un pacchetto [COPR]: [`scrcpy`][copr-link].
|
||||||
|
|
||||||
|
[COPR]: https://fedoraproject.org/wiki/Category:Copr
|
||||||
|
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
|
||||||
|
|
||||||
|
Per Arch Linux, è disponibile un pacchetto [AUR]: [`scrcpy`][aur-link].
|
||||||
|
|
||||||
|
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
|
||||||
|
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
|
||||||
|
|
||||||
|
Per Gentoo, è disponibile una [Ebuild]: [`scrcpy/`][ebuild-link].
|
||||||
|
|
||||||
|
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
|
||||||
|
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
|
||||||
|
|
||||||
|
Puoi anche [compilare l'app manualmente][BUILD] (in inglese) ([procedimento semplificato][BUILD_simple] (in inglese)).
|
||||||
|
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
Per Windows, per semplicità è disponibile un archivio precompilato con tutte le dipendenze (incluso `adb`):
|
||||||
|
|
||||||
|
- [README](README.md#windows) (Link al README originale per l'ultima versione)
|
||||||
|
|
||||||
|
È anche disponibile in [Chocolatey]:
|
||||||
|
|
||||||
|
[Chocolatey]: https://chocolatey.org/
|
||||||
|
|
||||||
|
```bash
|
||||||
|
choco install scrcpy
|
||||||
|
choco install adb # se non lo hai già
|
||||||
|
```
|
||||||
|
|
||||||
|
E in [Scoop]:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scoop install scrcpy
|
||||||
|
scoop install adb # se non lo hai già
|
||||||
|
```
|
||||||
|
|
||||||
|
[Scoop]: https://scoop.sh
|
||||||
|
|
||||||
|
Puoi anche [compilare l'app manualmente][BUILD] (in inglese).
|
||||||
|
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
|
||||||
|
L'applicazione è disponibile in [Homebrew]. Basta installarlo:
|
||||||
|
|
||||||
|
[Homebrew]: https://brew.sh/
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
Serve che `adb` sia accessibile dal tuo `PATH`. Se non lo hai già:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install android-platform-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
È anche disponibile in [MacPorts], che imposta adb per te:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo port install scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
[MacPorts]: https://www.macports.org/
|
||||||
|
|
||||||
|
|
||||||
|
Puoi anche [compilare l'app manualmente][BUILD] (in inglese).
|
||||||
|
|
||||||
|
|
||||||
|
## Esecuzione
|
||||||
|
|
||||||
|
Collega un dispositivo Android ed esegui:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
Scrcpy accetta argomenti da riga di comando, essi sono listati con:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Funzionalità
|
||||||
|
|
||||||
|
### Configurazione di acquisizione
|
||||||
|
|
||||||
|
#### Riduci dimensione
|
||||||
|
|
||||||
|
Qualche volta è utile trasmettere un dispositvo Android ad una definizione inferiore per aumentare le prestazioni.
|
||||||
|
|
||||||
|
Per limitare sia larghezza che altezza ad un certo valore (ad es. 1024):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --max-size 1024
|
||||||
|
scrcpy -m 1024 # versione breve
|
||||||
|
```
|
||||||
|
|
||||||
|
L'altra dimensione è calcolata in modo tale che il rapporto di forma del dispositivo sia preservato.
|
||||||
|
In questo esempio un dispositivo in 1920x1080 viene trasmesso a 1024x576.
|
||||||
|
|
||||||
|
|
||||||
|
#### Cambia bit-rate (velocità di trasmissione)
|
||||||
|
|
||||||
|
Il bit-rate predefinito è 8 Mbps. Per cambiare il bitrate video (ad es. a 2 Mbps):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --bit-rate 2M
|
||||||
|
scrcpy -b 2M # versione breve
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Limitare il frame rate (frequenza di fotogrammi)
|
||||||
|
|
||||||
|
Il frame rate di acquisizione può essere limitato:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --max-fps 15
|
||||||
|
```
|
||||||
|
|
||||||
|
Questo è supportato ufficialmente a partire da Android 10, ma potrebbe funzionare in versioni precedenti.
|
||||||
|
|
||||||
|
#### Ritaglio
|
||||||
|
|
||||||
|
Lo schermo del dispositivo può essere ritagliato per visualizzare solo parte di esso.
|
||||||
|
|
||||||
|
Questo può essere utile, per esempio, per trasmettere solo un occhio dell'Oculus Go:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
|
||||||
|
```
|
||||||
|
|
||||||
|
Se anche `--max-size` è specificata, il ridimensionamento è applicato dopo il ritaglio.
|
||||||
|
|
||||||
|
|
||||||
|
#### Blocca orientamento del video
|
||||||
|
|
||||||
|
|
||||||
|
Per bloccare l'orientamento della trasmissione:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --lock-video-orientation 0 # orientamento naturale
|
||||||
|
scrcpy --lock-video-orientation 1 # 90° antiorario
|
||||||
|
scrcpy --lock-video-orientation 2 # 180°
|
||||||
|
scrcpy --lock-video-orientation 3 # 90° orario
|
||||||
|
```
|
||||||
|
|
||||||
|
Questo influisce sull'orientamento della registrazione.
|
||||||
|
|
||||||
|
|
||||||
|
La [finestra può anche essere ruotata](#rotazione) indipendentemente.
|
||||||
|
|
||||||
|
|
||||||
|
#### Codificatore
|
||||||
|
|
||||||
|
Alcuni dispositivi hanno più di un codificatore e alcuni di questi possono provocare problemi o crash. È possibile selezionare un encoder diverso:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --encoder OMX.qcom.video.encoder.avc
|
||||||
|
```
|
||||||
|
|
||||||
|
Per elencare i codificatori disponibili puoi immettere un nome di codificatore non valido e l'errore mostrerà i codificatori disponibili:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --encoder _
|
||||||
|
```
|
||||||
|
|
||||||
|
### Registrazione
|
||||||
|
|
||||||
|
È possibile registrare lo schermo durante la trasmissione:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --record file.mp4
|
||||||
|
scrcpy -r file.mkv
|
||||||
|
```
|
||||||
|
|
||||||
|
Per disabilitare la trasmissione durante la registrazione:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-display --record file.mp4
|
||||||
|
scrcpy -Nr file.mkv
|
||||||
|
# interrompere la registrazione con Ctrl+C
|
||||||
|
```
|
||||||
|
|
||||||
|
I "fotogrammi saltati" sono registrati nonostante non siano mostrati in tempo reale (per motivi di prestazioni). I fotogrammi sono _datati_ sul dispositivo, così una [variazione di latenza dei pacchetti][packet delay variation] non impatta il file registrato.
|
||||||
|
|
||||||
|
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
||||||
|
|
||||||
|
|
||||||
|
### Connessione
|
||||||
|
|
||||||
|
#### Wireless
|
||||||
|
|
||||||
|
|
||||||
|
_Scrcpy_ usa `adb` per comunicare col dispositivo e `adb` può [connettersi][connect] al dispositivo mediante TCP/IP:
|
||||||
|
|
||||||
|
1. Connetti il dispositivo alla stessa rete Wi-Fi del tuo computer.
|
||||||
|
2. Trova l'indirizzo IP del tuo dispositivo in Impostazioni → Informazioni sul telefono → Stato, oppure eseguendo questo comando:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb shell ip route | awk '{print $9}'
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Abilita adb via TCP/IP sul tuo dispositivo: `adb tcpip 5555`.
|
||||||
|
4. Scollega il tuo dispositivo.
|
||||||
|
5. Connetti il tuo dispositivo: `adb connect IP_DISPOSITVO:5555` _(rimpiazza `IP_DISPOSITIVO`)_.
|
||||||
|
6. Esegui `scrcpy` come al solito.
|
||||||
|
|
||||||
|
Potrebbe essere utile diminuire il bit-rate e la definizione
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --bit-rate 2M --max-size 800
|
||||||
|
scrcpy -b2M -m800 # versione breve
|
||||||
|
```
|
||||||
|
|
||||||
|
[connect]: https://developer.android.com/studio/command-line/adb.html#wireless
|
||||||
|
|
||||||
|
|
||||||
|
#### Multi dispositivo
|
||||||
|
|
||||||
|
Se in `adb devices` sono listati più dispositivi, è necessario specificare il _seriale_:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --serial 0123456789abcdef
|
||||||
|
scrcpy -s 0123456789abcdef # versione breve
|
||||||
|
```
|
||||||
|
|
||||||
|
Se il dispositivo è collegato mediante TCP/IP:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --serial 192.168.0.1:5555
|
||||||
|
scrcpy -s 192.168.0.1:5555 # versione breve
|
||||||
|
```
|
||||||
|
|
||||||
|
Puoi avviare più istanze di _scrcpy_ per diversi dispositivi.
|
||||||
|
|
||||||
|
|
||||||
|
#### Avvio automativo alla connessione del dispositivo
|
||||||
|
|
||||||
|
Potresti usare [AutoAdb]:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
autoadb scrcpy -s '{}'
|
||||||
|
```
|
||||||
|
|
||||||
|
[AutoAdb]: https://github.com/rom1v/autoadb
|
||||||
|
|
||||||
|
#### Tunnel SSH
|
||||||
|
|
||||||
|
Per connettersi a un dispositivo remoto è possibile collegare un client `adb` locale ad un server `adb` remoto (assunto che entrambi stiano usando la stessa versione del protocollo _adb_):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb kill-server # termina il server adb locale su 5037
|
||||||
|
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
||||||
|
# tieni questo aperto
|
||||||
|
```
|
||||||
|
|
||||||
|
Da un altro terminale:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
Per evitare l'abilitazione dell'apertura porte remota potresti invece forzare una "forward connection" (notare il `-L` invece di `-R`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb kill-server # termina il server adb locale su 5037
|
||||||
|
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
||||||
|
# tieni questo aperto
|
||||||
|
```
|
||||||
|
|
||||||
|
Da un altro terminale:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --force-adb-forward
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Come per le connessioni wireless potrebbe essere utile ridurre la qualità:
|
||||||
|
|
||||||
|
```
|
||||||
|
scrcpy -b2M -m800 --max-fps 15
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configurazione della finestra
|
||||||
|
|
||||||
|
#### Titolo
|
||||||
|
|
||||||
|
Il titolo della finestra è il modello del dispositivo per impostazione predefinita. Esso può essere cambiato:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --window-title 'My device'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Posizione e dimensione
|
||||||
|
|
||||||
|
La posizione e la dimensione iniziale della finestra può essere specificata:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Senza bordi
|
||||||
|
|
||||||
|
Per disabilitare le decorazioni della finestra:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --window-borderless
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Sempre in primo piano
|
||||||
|
|
||||||
|
Per tenere scrcpy sempre in primo piano:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --always-on-top
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Schermo intero
|
||||||
|
|
||||||
|
L'app può essere avviata direttamente a schermo intero:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --fullscreen
|
||||||
|
scrcpy -f # versione breve
|
||||||
|
```
|
||||||
|
|
||||||
|
Lo schermo intero può anche essere attivato/disattivato con <kbd>MOD</kbd>+<kbd>f</kbd>.
|
||||||
|
|
||||||
|
#### Rotazione
|
||||||
|
|
||||||
|
La finestra può essere ruotata:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --rotation 1
|
||||||
|
```
|
||||||
|
|
||||||
|
I valori possibili sono:
|
||||||
|
- `0`: nessuna rotazione
|
||||||
|
- `1`: 90 gradi antiorari
|
||||||
|
- `2`: 180 gradi
|
||||||
|
- `3`: 90 gradi orari
|
||||||
|
|
||||||
|
La rotazione può anche essere cambiata dinamicamente con <kbd>MOD</kbd>+<kbd>←</kbd>
|
||||||
|
_(sinistra)_ e <kbd>MOD</kbd>+<kbd>→</kbd> _(destra)_.
|
||||||
|
|
||||||
|
Notare che _scrcpy_ gestisce 3 diversi tipi di rotazione:
|
||||||
|
- <kbd>MOD</kbd>+<kbd>r</kbd> richiede al dispositvo di cambiare tra orientamento verticale (portrait) e orizzontale (landscape) (l'app in uso potrebbe rifiutarsi se non supporta l'orientamento richiesto).
|
||||||
|
- [`--lock-video-orientation`](#blocca-orientamento-del-video) cambia l'orientamento della trasmissione (l'orientamento del video inviato dal dispositivo al computer). Questo influenza la registrazione.
|
||||||
|
- `--rotation` (o <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>) ruota solo il contenuto della finestra. Questo influenza solo la visualizzazione, non la registrazione.
|
||||||
|
|
||||||
|
|
||||||
|
### Altre opzioni di trasmissione
|
||||||
|
|
||||||
|
#### "Sola lettura"
|
||||||
|
|
||||||
|
Per disabilitare i controlli (tutto ciò che può interagire col dispositivo: tasti di input, eventi del mouse, trascina e rilascia (drag&drop) file):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-control
|
||||||
|
scrcpy -n
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Schermo
|
||||||
|
|
||||||
|
Se sono disponibili più schermi, è possibile selezionare lo schermo da trasmettere:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --display 1
|
||||||
|
```
|
||||||
|
|
||||||
|
La lista degli id schermo può essere ricavata da:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb shell dumpsys display # cerca "mDisplayId=" nell'output
|
||||||
|
```
|
||||||
|
|
||||||
|
Lo schermo secondario potrebbe essere possibile controllarlo solo se il dispositivo esegue almeno Android 10 (in caso contrario è trasmesso in modalità sola lettura).
|
||||||
|
|
||||||
|
|
||||||
|
#### Mantenere sbloccato
|
||||||
|
|
||||||
|
Per evitare che il dispositivo si blocchi dopo un po' che il dispositivo è collegato:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --stay-awake
|
||||||
|
scrcpy -w
|
||||||
|
```
|
||||||
|
|
||||||
|
Lo stato iniziale è ripristinato quando scrcpy viene chiuso.
|
||||||
|
|
||||||
|
|
||||||
|
#### Spegnere lo schermo
|
||||||
|
|
||||||
|
È possibile spegnere lo schermo del dispositivo durante la trasmissione con un'opzione da riga di comando:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --turn-screen-off
|
||||||
|
scrcpy -S
|
||||||
|
```
|
||||||
|
|
||||||
|
Oppure premendo <kbd>MOD</kbd>+<kbd>o</kbd> in qualsiasi momento.
|
||||||
|
|
||||||
|
Per riaccenderlo premere <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
|
||||||
|
|
||||||
|
In Android il pulsante `POWER` (tasto di accensione) accende sempre lo schermo. Per comodità, se `POWER` è inviato via scrcpy (con click destro o con <kbd>MOD</kbd>+<kbd>p</kbd>), si forza il dispositivo a spegnere lo schermo dopo un piccolo ritardo (appena possibile).
|
||||||
|
Il pulsante fisico `POWER` continuerà ad accendere lo schermo normalmente.
|
||||||
|
|
||||||
|
Può anche essere utile evitare il blocco del dispositivo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --turn-screen-off --stay-awake
|
||||||
|
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
|
||||||
|
|
||||||
|
Per le presentazioni può essere utile mostrare i tocchi fisici (sul dispositivo fisico).
|
||||||
|
|
||||||
|
Android fornisce questa funzionalità nelle _Opzioni sviluppatore_.
|
||||||
|
|
||||||
|
_Scrcpy_ fornisce un'opzione per abilitare questa funzionalità all'avvio e ripristinare il valore iniziale alla chiusura:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --show-touches
|
||||||
|
scrcpy -t
|
||||||
|
```
|
||||||
|
|
||||||
|
Notare che mostra solo i tocchi _fisici_ (con le dita sul dispositivo).
|
||||||
|
|
||||||
|
|
||||||
|
#### Disabilitare il salvaschermo
|
||||||
|
|
||||||
|
In maniera predefinita scrcpy non previene l'attivazione del salvaschermo del computer.
|
||||||
|
|
||||||
|
Per disabilitarlo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --disable-screensaver
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Input di controlli
|
||||||
|
|
||||||
|
#### Rotazione dello schermo del dispostivo
|
||||||
|
|
||||||
|
Premere <kbd>MOD</kbd>+<kbd>r</kbd> per cambiare tra le modalità verticale (portrait) e orizzontale (landscape).
|
||||||
|
|
||||||
|
Notare che la rotazione avviene solo se l'applicazione in primo piano supporta l'orientamento richiesto.
|
||||||
|
|
||||||
|
#### Copia-incolla
|
||||||
|
|
||||||
|
Quando gli appunti di Android cambiano, essi vengono automaticamente sincronizzati con gli appunti del computer.
|
||||||
|
|
||||||
|
Qualsiasi scorciatoia <kbd>Ctrl</kbd> viene inoltrata al dispositivo. In particolare:
|
||||||
|
- <kbd>Ctrl</kbd>+<kbd>c</kbd> copia
|
||||||
|
- <kbd>Ctrl</kbd>+<kbd>x</kbd> taglia
|
||||||
|
- <kbd>Ctrl</kbd>+<kbd>v</kbd> incolla (dopo la sincronizzazione degli appunti da computer a dispositivo)
|
||||||
|
|
||||||
|
Questo solitamente funziona nella maniera più comune.
|
||||||
|
|
||||||
|
Il comportamento reale, però, dipende dall'applicazione attiva. Per esempio _Termux_ invia SIGINT con <kbd>Ctrl</kbd>+<kbd>c</kbd>, e _K-9 Mail_ compone un nuovo messaggio.
|
||||||
|
|
||||||
|
Per copiare, tagliare e incollare in questi casi (ma è solo supportato in Android >= 7):
|
||||||
|
- <kbd>MOD</kbd>+<kbd>c</kbd> inietta `COPY`
|
||||||
|
- <kbd>MOD</kbd>+<kbd>x</kbd> inietta `CUT`
|
||||||
|
- <kbd>MOD</kbd>+<kbd>v</kbd> inietta `PASTE` (dopo la sincronizzazione degli appunti da computer a dispositivo)
|
||||||
|
|
||||||
|
In aggiunta, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> permette l'iniezione del testo degli appunti del computer come una sequenza di eventi pressione dei tasti. Questo è utile quando il componente non accetta l'incollaggio di testo (per esempio in _Termux_), ma questo può rompere il contenuto non ASCII.
|
||||||
|
|
||||||
|
**AVVISO:** Incollare gli appunti del computer nel dispositivo (sia con <kbd>Ctrl</kbd>+<kbd>v</kbd> che con <kbd>MOD</kbd>+<kbd>v</kbd>) copia il contenuto negli appunti del dispositivo. Come conseguenza, qualsiasi applicazione Android potrebbe leggere il suo contenuto. Dovresti evitare di incollare contenuti sensibili (come password) in questa maniera.
|
||||||
|
|
||||||
|
Alcuni dispositivi non si comportano come aspettato quando si modificano gli appunti del dispositivo a livello di codice. L'opzione `--legacy-paste` è fornita per cambiare il comportamento di <kbd>Ctrl</kbd>+<kbd>v</kbd> and <kbd>MOD</kbd>+<kbd>v</kbd> in modo tale che anch'essi iniettino il testo gli appunti del computer come una sequenza di eventi pressione dei tasti (nella stessa maniera di <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
|
||||||
|
|
||||||
|
#### Pizzica per zoomare (pinch-to-zoom)
|
||||||
|
|
||||||
|
Per simulare il "pizzica per zoomare": <kbd>Ctrl</kbd>+_click e trascina_.
|
||||||
|
|
||||||
|
Più precisamente, tieni premuto <kbd>Ctrl</kbd> mentre premi il pulsante sinistro. Finchè il pulsante non sarà rilasciato, tutti i movimenti del mouse ridimensioneranno e ruoteranno il contenuto (se supportato dall'applicazione) relativamente al centro dello schermo.
|
||||||
|
|
||||||
|
Concretamente scrcpy genera degli eventi di tocco addizionali di un "dito virtuale" nella posizione simmetricamente opposta rispetto al centro dello schermo.
|
||||||
|
|
||||||
|
|
||||||
|
#### Preferenze di iniezione del testo
|
||||||
|
|
||||||
|
Ci sono due tipi di [eventi][textevents] generati quando si scrive testo:
|
||||||
|
- _eventi di pressione_, segnalano che tasto è stato premuto o rilasciato;
|
||||||
|
- _eventi di testo_, segnalano che del testo è stato inserito.
|
||||||
|
|
||||||
|
In maniera predefinita le lettere sono "iniettate" usando gli eventi di pressione, in maniera tale che la tastiera si comporti come aspettato nei giochi (come accade solitamente per i tasti WASD).
|
||||||
|
|
||||||
|
Questo, però, può [causare problemi][prefertext]. Se incontri un problema del genere, puoi evitarlo con:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --prefer-text
|
||||||
|
```
|
||||||
|
|
||||||
|
(ma questo romperà il normale funzionamento della tastiera nei giochi)
|
||||||
|
|
||||||
|
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
||||||
|
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
||||||
|
|
||||||
|
|
||||||
|
#### Ripetizione di tasti
|
||||||
|
|
||||||
|
In maniera predefinita tenere premuto un tasto genera una ripetizione degli eventi di pressione di tale tasto. Questo può creare problemi di performance in alcuni giochi, dove questi eventi sono inutilizzati.
|
||||||
|
|
||||||
|
Per prevenire l'inoltro ripetuto degli eventi di pressione:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-key-repeat
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Click destro e click centrale
|
||||||
|
|
||||||
|
In maniera predefinita, click destro aziona BACK (indietro) e il click centrale aziona HOME. Per disabilitare queste scorciatoie e, invece, inviare i click al dispositivo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --forward-all-clicks
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Rilascio di file
|
||||||
|
|
||||||
|
#### Installare APK
|
||||||
|
|
||||||
|
Per installare un APK, trascina e rilascia un file APK (finisce con `.apk`) nella finestra di _scrcpy_.
|
||||||
|
|
||||||
|
Non c'è alcuna risposta visiva, un log è stampato nella console.
|
||||||
|
|
||||||
|
|
||||||
|
#### Trasferimento di file verso il dispositivo
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
La cartella di destinazione può essere cambiata all'avvio:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --push-target=/sdcard/Download/
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Inoltro dell'audio
|
||||||
|
|
||||||
|
L'audio non è inoltrato da _scrcpy_. Usa [sndcpy].
|
||||||
|
|
||||||
|
Vedi anche la [issue #14].
|
||||||
|
|
||||||
|
[sndcpy]: https://github.com/rom1v/sndcpy
|
||||||
|
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
|
||||||
|
|
||||||
|
|
||||||
|
## Scociatoie
|
||||||
|
|
||||||
|
Nella lista seguente, <kbd>MOD</kbd> è il modificatore delle scorciatoie. In maniera predefinita è <kbd>Alt</kbd> (sinistro) o <kbd>Super</kbd> (sinistro).
|
||||||
|
|
||||||
|
Può essere cambiato usando `--shortcut-mod`. I tasti possibili sono `lctrl`, `rctrl`, `lalt`, `ralt`, `lsuper` and `rsuper` (`l` significa sinistro e `r` significa destro). Per esempio:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# usa ctrl destro per le scorciatoie
|
||||||
|
scrcpy --shortcut-mod=rctrl
|
||||||
|
|
||||||
|
# use sia "ctrl sinistro"+"alt sinistro" che "super sinistro" per le scorciatoie
|
||||||
|
scrcpy --shortcut-mod=lctrl+lalt,lsuper
|
||||||
|
```
|
||||||
|
|
||||||
|
_<kbd>[Super]</kbd> è il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kbd>._
|
||||||
|
|
||||||
|
[Super]: https://it.wikipedia.org/wiki/Tasto_Windows
|
||||||
|
<!-- https://en.wikipedia.org/wiki/Super_key_(keyboard_button) è la pagina originale di Wikipedia inglese, l'ho sostituita con una simile in quello italiano -->
|
||||||
|
|
||||||
|
| Azione | Scorciatoia
|
||||||
|
| ------------------------------------------- |:-----------------------------
|
||||||
|
| Schermo intero | <kbd>MOD</kbd>+<kbd>f</kbd>
|
||||||
|
| Rotazione schermo a sinistra | <kbd>MOD</kbd>+<kbd>←</kbd> _(sinistra)_
|
||||||
|
| Rotazione schermo a destra | <kbd>MOD</kbd>+<kbd>→</kbd> _(destra)_
|
||||||
|
| 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¹_
|
||||||
|
| 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 `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</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_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(giù)_
|
||||||
|
| Premi il tasto `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
|
||||||
|
| Accendi | _Click destro²_
|
||||||
|
| 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>
|
||||||
|
| Ruota lo schermo del dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
|
||||||
|
| Espandi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>n</kbd>
|
||||||
|
| Chiudi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||||
|
| Copia negli appunti³ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||||
|
| Taglia negli appunti³ | <kbd>MOD</kbd>+<kbd>x</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>
|
||||||
|
| Abilita/Disabilita il contatore FPS (su stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||||
|
| Pizzica per zoomare | <kbd>Ctrl</kbd>+_click e trascina_
|
||||||
|
|
||||||
|
_¹Doppio click sui bordi neri per rimuoverli._
|
||||||
|
_²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._
|
||||||
|
_³Solo in Android >= 7._
|
||||||
|
|
||||||
|
Tutte le scorciatoie <kbd>Ctrl</kbd>+_tasto_ sono inoltrate al dispositivo, così sono gestite dall'applicazione attiva.
|
||||||
|
|
||||||
|
## Path personalizzati
|
||||||
|
|
||||||
|
Per utilizzare dei binari _adb_ specifici, configura il suo path nella variabile d'ambente `ADB`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ADB=/percorso/per/adb scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
Per sovrascrivere il percorso del file `scrcpy-server`, configura il percorso in `SCRCPY_SERVER_PATH`.
|
||||||
|
|
||||||
|
## Perchè _scrcpy_?
|
||||||
|
|
||||||
|
Un collega mi ha sfidato a trovare un nome tanto impronunciabile quanto [gnirehtet].
|
||||||
|
|
||||||
|
[`strcpy`] copia una **str**ing (stringa); `scrcpy` copia uno **scr**een (schermo).
|
||||||
|
|
||||||
|
[gnirehtet]: https://github.com/Genymobile/gnirehtet
|
||||||
|
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
|
||||||
|
|
||||||
|
## Come compilare?
|
||||||
|
|
||||||
|
Vedi [BUILD] (in inglese).
|
||||||
|
|
||||||
|
|
||||||
|
## Problemi comuni
|
||||||
|
|
||||||
|
Vedi le [FAQ](FAQ.it.md).
|
||||||
|
|
||||||
|
|
||||||
|
## Sviluppatori
|
||||||
|
|
||||||
|
Leggi la [pagina per sviluppatori].
|
||||||
|
|
||||||
|
[pagina per sviluppatori]: DEVELOP.md
|
||||||
|
|
||||||
|
|
||||||
|
## Licenza (in inglese)
|
||||||
|
|
||||||
|
Copyright (C) 2018 Genymobile
|
||||||
|
Copyright (C) 2018-2021 Romain Vimont
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
## Articoli (in inglese)
|
||||||
|
|
||||||
|
- [Introducendo scrcpy][article-intro]
|
||||||
|
- [Scrcpy ora funziona wireless][article-tcpip]
|
||||||
|
|
||||||
|
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
|
||||||
|
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
|
||||||
@@ -112,7 +112,7 @@ scrcpy --help
|
|||||||
### 캡쳐 환경 설정
|
### 캡쳐 환경 설정
|
||||||
|
|
||||||
|
|
||||||
###사이즈 재정의
|
### 사이즈 재정의
|
||||||
|
|
||||||
가끔씩 성능을 향상시키기위해 안드로이드 디바이스를 낮은 해상도에서 미러링하는 것이 유용할 때도 있습니다.
|
가끔씩 성능을 향상시키기위해 안드로이드 디바이스를 낮은 해상도에서 미러링하는 것이 유용할 때도 있습니다.
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ scrcpy --bit-rate 2M
|
|||||||
scrcpy -b 2M # 축약 버전
|
scrcpy -b 2M # 축약 버전
|
||||||
```
|
```
|
||||||
|
|
||||||
###프레임 비율 제한
|
### 프레임 비율 제한
|
||||||
|
|
||||||
안드로이드 버전 10이상의 디바이스에서는, 다음의 명령어로 캡쳐 화면의 프레임 비율을 제한할 수 있습니다:
|
안드로이드 버전 10이상의 디바이스에서는, 다음의 명령어로 캡쳐 화면의 프레임 비율을 제한할 수 있습니다:
|
||||||
|
|
||||||
|
|||||||
132
README.md
132
README.md
@@ -127,13 +127,18 @@ brew install scrcpy
|
|||||||
You need `adb`, accessible from your `PATH`. If you don't have it yet:
|
You need `adb`, accessible from your `PATH`. If you don't have it yet:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Homebrew >= 2.6.0
|
brew install android-platform-tools
|
||||||
brew install --cask android-platform-tools
|
|
||||||
|
|
||||||
# Homebrew < 2.6.0
|
|
||||||
brew cask install android-platform-tools
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
It's also available in [MacPorts], which sets up adb for you:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo port install scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
[MacPorts]: https://www.macports.org/
|
||||||
|
|
||||||
|
|
||||||
You can also [build the app manually][BUILD].
|
You can also [build the app manually][BUILD].
|
||||||
|
|
||||||
|
|
||||||
@@ -209,10 +214,11 @@ If `--max-size` is also specified, resizing is applied after cropping.
|
|||||||
To lock the orientation of the mirroring:
|
To lock the orientation of the mirroring:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --lock-video-orientation 0 # natural orientation
|
scrcpy --lock-video-orientation # initial (current) orientation
|
||||||
scrcpy --lock-video-orientation 1 # 90° counterclockwise
|
scrcpy --lock-video-orientation=0 # natural orientation
|
||||||
scrcpy --lock-video-orientation 2 # 180°
|
scrcpy --lock-video-orientation=1 # 90° counterclockwise
|
||||||
scrcpy --lock-video-orientation 3 # 90° clockwise
|
scrcpy --lock-video-orientation=2 # 180°
|
||||||
|
scrcpy --lock-video-orientation=3 # 90° clockwise
|
||||||
```
|
```
|
||||||
|
|
||||||
This affects recording orientation.
|
This affects recording orientation.
|
||||||
@@ -236,7 +242,9 @@ error will give the available encoders:
|
|||||||
scrcpy --encoder _
|
scrcpy --encoder _
|
||||||
```
|
```
|
||||||
|
|
||||||
### Recording
|
### Capture
|
||||||
|
|
||||||
|
#### Recording
|
||||||
|
|
||||||
It is possible to record the screen while mirroring:
|
It is possible to record the screen while mirroring:
|
||||||
|
|
||||||
@@ -260,6 +268,59 @@ variation] does not impact the recorded file.
|
|||||||
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
||||||
|
|
||||||
|
|
||||||
|
#### v4l2loopback
|
||||||
|
|
||||||
|
On Linux, it is possible to send the video stream to a v4l2 loopback device, so
|
||||||
|
that the Android device can be opened like a webcam by any v4l2-capable tool.
|
||||||
|
|
||||||
|
The module `v4l2loopback` must be installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install v4l2loopback-dkms
|
||||||
|
```
|
||||||
|
|
||||||
|
To create a v4l2 device:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo modprobe v4l2loopback
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create a new video device in `/dev/videoN`, where `N` is an integer
|
||||||
|
(more [options](https://github.com/umlaeute/v4l2loopback#options) are available
|
||||||
|
to create several devices or devices with specific IDs).
|
||||||
|
|
||||||
|
To list the enabled devices:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# requires v4l-utils package
|
||||||
|
v4l2-ctl --list-devices
|
||||||
|
|
||||||
|
# simple but might be sufficient
|
||||||
|
ls /dev/video*
|
||||||
|
```
|
||||||
|
|
||||||
|
To start scrcpy using a v4l2 sink:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --v4l2-sink=/dev/videoN
|
||||||
|
scrcpy --v4l2-sink=/dev/videoN --no-display # disable mirroring window
|
||||||
|
scrcpy --v4l2-sink=/dev/videoN -N # short version
|
||||||
|
```
|
||||||
|
|
||||||
|
(replace `N` by the device ID, check with `ls /dev/video*`)
|
||||||
|
|
||||||
|
Once enabled, you can open your video stream with a v4l2-capable tool:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ffplay -i /dev/videoN
|
||||||
|
vlc v4l2:///dev/videoN # VLC might add some buffering delay
|
||||||
|
```
|
||||||
|
|
||||||
|
For example, you could capture the video within [OBS].
|
||||||
|
|
||||||
|
[OBS]: https://obsproject.com/fr
|
||||||
|
|
||||||
|
|
||||||
### Connection
|
### Connection
|
||||||
|
|
||||||
#### Wireless
|
#### Wireless
|
||||||
@@ -455,7 +516,7 @@ scrcpy --display 1
|
|||||||
|
|
||||||
The list of display ids can be retrieved by:
|
The list of display ids can be retrieved by:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
adb shell dumpsys display # search "mDisplayId=" in the output
|
adb shell dumpsys display # search "mDisplayId=" in the output
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -502,18 +563,6 @@ scrcpy -Sw
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### Render expired frames
|
|
||||||
|
|
||||||
By default, to minimize latency, _scrcpy_ always renders the last decoded frame
|
|
||||||
available, and drops any previous one.
|
|
||||||
|
|
||||||
To force the rendering of all frames (at a cost of a possible increased
|
|
||||||
latency), use:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --render-expired-frames
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Show touches
|
#### Show touches
|
||||||
|
|
||||||
For presentations, it may be useful to show physical touches (on the physical
|
For presentations, it may be useful to show physical touches (on the physical
|
||||||
@@ -661,15 +710,15 @@ There is no visual feedback, a log is printed to the console.
|
|||||||
|
|
||||||
#### Push file to device
|
#### Push file to device
|
||||||
|
|
||||||
To push a file to `/sdcard/` on the device, drag & drop a (non-APK) file to the
|
To push a file to `/sdcard/Download/` on the device, drag & drop a (non-APK)
|
||||||
_scrcpy_ window.
|
file to the _scrcpy_ window.
|
||||||
|
|
||||||
There is no visual feedback, a log is printed to the console.
|
There is no visual feedback, a log is printed to the console.
|
||||||
|
|
||||||
The target directory can be changed on start:
|
The target directory can be changed on start:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --push-target=/sdcard/Download/
|
scrcpy --push-target=/sdcard/Movies/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -709,10 +758,10 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
|||||||
| Rotate display left | <kbd>MOD</kbd>+<kbd>←</kbd> _(left)_
|
| Rotate display left | <kbd>MOD</kbd>+<kbd>←</kbd> _(left)_
|
||||||
| Rotate display right | <kbd>MOD</kbd>+<kbd>→</kbd> _(right)_
|
| Rotate display right | <kbd>MOD</kbd>+<kbd>→</kbd> _(right)_
|
||||||
| Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
| Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
||||||
| Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-click¹_
|
| Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_
|
||||||
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
|
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
|
||||||
| Click on `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Right-click²_
|
| Click on `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Right-click²_
|
||||||
| Click on `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd>
|
| Click on `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4th-click³_
|
||||||
| Click on `MENU` (unlock screen) | <kbd>MOD</kbd>+<kbd>m</kbd>
|
| Click on `MENU` (unlock screen) | <kbd>MOD</kbd>+<kbd>m</kbd>
|
||||||
| Click on `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(up)_
|
| Click on `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(up)_
|
||||||
| Click on `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(down)_
|
| Click on `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(down)_
|
||||||
@@ -721,18 +770,27 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
|||||||
| Turn device screen off (keep mirroring) | <kbd>MOD</kbd>+<kbd>o</kbd>
|
| Turn device screen off (keep mirroring) | <kbd>MOD</kbd>+<kbd>o</kbd>
|
||||||
| 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>
|
| Expand notification panel | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5th-click³_
|
||||||
| Collapse notification panel | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
| Expand settings panel | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Double-5th-click³_
|
||||||
| Copy to clipboard³ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
| Collapse panels | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||||
| Cut to clipboard³ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
| Copy to clipboard⁴ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||||
| Synchronize clipboards and paste³ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
| Cut to clipboard⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||||
|
| Synchronize clipboards and paste⁴ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||||
| 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_
|
||||||
|
|
||||||
_¹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._
|
||||||
_³Only on Android >= 7._
|
_³4th and 5th mouse buttons, if your mouse has them._
|
||||||
|
_⁴Only on Android >= 7._
|
||||||
|
|
||||||
|
Shortcuts with repeated keys are executted by releasing and pressing the key a
|
||||||
|
second time. For example, to execute "Expand settings panel":
|
||||||
|
|
||||||
|
1. Press and keep pressing <kbd>MOD</kbd>.
|
||||||
|
2. Then double-press <kbd>n</kbd>.
|
||||||
|
3. Finally, release <kbd>MOD</kbd>.
|
||||||
|
|
||||||
All <kbd>Ctrl</kbd>+_key_ shortcuts are forwarded to the device, so they are
|
All <kbd>Ctrl</kbd>+_key_ shortcuts are forwarded to the device, so they are
|
||||||
handled by the active application.
|
handled by the active application.
|
||||||
@@ -743,7 +801,9 @@ handled by the active application.
|
|||||||
To use a specific _adb_ binary, configure its path in the environment variable
|
To use a specific _adb_ binary, configure its path in the environment variable
|
||||||
`ADB`:
|
`ADB`:
|
||||||
|
|
||||||
ADB=/path/to/adb scrcpy
|
```bash
|
||||||
|
ADB=/path/to/adb scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
To override the path of the `scrcpy-server` file, configure its path in
|
To override the path of the `scrcpy-server` file, configure its path in
|
||||||
`SCRCPY_SERVER_PATH`.
|
`SCRCPY_SERVER_PATH`.
|
||||||
@@ -808,9 +868,11 @@ 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.17](README.it.md)
|
||||||
- [日本語 (Japanese, `jp`) - v1.17](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.17](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)
|
||||||
- [简体中文 (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)
|
||||||
|
|
||||||
|
|||||||
743
README.sp.md
Normal file
743
README.sp.md
Normal file
@@ -0,0 +1,743 @@
|
|||||||
|
Solo se garantiza que el archivo [README](README.md) original esté actualizado.
|
||||||
|
|
||||||
|
# scrcpy (v1.17)
|
||||||
|
|
||||||
|
Esta aplicación proporciona imagen y control de un dispositivo Android conectado
|
||||||
|
por USB (o [por TCP/IP][article-tcpip]). No requiere acceso _root_.
|
||||||
|
Compatible con _GNU/Linux_, _Windows_ y _macOS_.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Sus características principales son:
|
||||||
|
|
||||||
|
- **ligero** (nativo, solo muestra la imagen del dispositivo)
|
||||||
|
- **desempeño** (30~60fps)
|
||||||
|
- **calidad** (1920×1080 o superior)
|
||||||
|
- **baja latencia** ([35~70ms][lowlatency])
|
||||||
|
- **corto tiempo de inicio** (~1 segundo para mostrar la primera imagen)
|
||||||
|
- **no intrusivo** (no se deja nada instalado en el dispositivo)
|
||||||
|
|
||||||
|
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
|
||||||
|
|
||||||
|
|
||||||
|
## Requisitos
|
||||||
|
|
||||||
|
El dispositivo Android requiere como mínimo API 21 (Android 5.0).
|
||||||
|
|
||||||
|
Asegurate de [habilitar el adb debugging][enable-adb] en tu(s) dispositivo(s).
|
||||||
|
|
||||||
|
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
|
||||||
|
|
||||||
|
En algunos dispositivos, también necesitas habilitar [una opción adicional][control] para controlarlo con el teclado y ratón.
|
||||||
|
|
||||||
|
[control]: https://github.com/Genymobile/scrcpy/issues/70#issuecomment-373286323
|
||||||
|
|
||||||
|
|
||||||
|
## Consigue la app
|
||||||
|
|
||||||
|
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
|
||||||
|
|
||||||
|
### Resumen
|
||||||
|
|
||||||
|
- Linux: `apt install scrcpy`
|
||||||
|
- Windows: [download](README.md#windows)
|
||||||
|
- macOS: `brew install scrcpy`
|
||||||
|
|
||||||
|
Construir desde la fuente: [BUILD] ([proceso simplificado][BUILD_simple])
|
||||||
|
|
||||||
|
[BUILD]: BUILD.md
|
||||||
|
[BUILD_simple]: BUILD.md#simple
|
||||||
|
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
En Debian (_test_ y _sid_ por ahora) y Ubuntu (20.04):
|
||||||
|
|
||||||
|
```
|
||||||
|
apt install scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
Hay un paquete [Snap]: [`scrcpy`][snap-link].
|
||||||
|
|
||||||
|
[snap-link]: https://snapstats.org/snaps/scrcpy
|
||||||
|
|
||||||
|
[snap]: https://en.wikipedia.org/wiki/Snappy_(package_manager)
|
||||||
|
|
||||||
|
Para Fedora, hay un paquete [COPR]: [`scrcpy`][copr-link].
|
||||||
|
|
||||||
|
[COPR]: https://fedoraproject.org/wiki/Category:Copr
|
||||||
|
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
|
||||||
|
|
||||||
|
Para Arch Linux, hay un paquete [AUR]: [`scrcpy`][aur-link].
|
||||||
|
|
||||||
|
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
|
||||||
|
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
|
||||||
|
|
||||||
|
Para Gentoo, hay un paquete [Ebuild]: [`scrcpy/`][ebuild-link].
|
||||||
|
|
||||||
|
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
|
||||||
|
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
|
||||||
|
|
||||||
|
También puedes [construir la aplicación manualmente][BUILD] ([proceso simplificado][BUILD_simple]).
|
||||||
|
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
Para Windows, por simplicidad, hay un pre-compilado con todas las dependencias
|
||||||
|
(incluyendo `adb`):
|
||||||
|
|
||||||
|
- [README](README.md#windows)
|
||||||
|
|
||||||
|
También está disponible en [Chocolatey]:
|
||||||
|
|
||||||
|
[Chocolatey]: https://chocolatey.org/
|
||||||
|
|
||||||
|
```bash
|
||||||
|
choco install scrcpy
|
||||||
|
choco install adb # si aún no está instalado
|
||||||
|
```
|
||||||
|
|
||||||
|
Y en [Scoop]:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scoop install scrcpy
|
||||||
|
scoop install adb # si aún no está instalado
|
||||||
|
```
|
||||||
|
|
||||||
|
[Scoop]: https://scoop.sh
|
||||||
|
|
||||||
|
También puedes [construir la aplicación manualmente][BUILD].
|
||||||
|
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
|
||||||
|
La aplicación está disponible en [Homebrew]. Solo instalala:
|
||||||
|
|
||||||
|
[Homebrew]: https://brew.sh/
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
Necesitarás `adb`, accesible desde `PATH`. Si aún no lo tienes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install android-platform-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
También está disponible en [MacPorts], que configurará el adb automáticamente:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo port install scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
[MacPorts]: https://www.macports.org/
|
||||||
|
|
||||||
|
|
||||||
|
También puedes [construir la aplicación manualmente][BUILD].
|
||||||
|
|
||||||
|
|
||||||
|
## Ejecutar
|
||||||
|
|
||||||
|
Enchufa el dispositivo Android, y ejecuta:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
Acepta argumentos desde la línea de comandos, listados en:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Características
|
||||||
|
|
||||||
|
### Capturar configuración
|
||||||
|
|
||||||
|
#### Reducir la definición
|
||||||
|
|
||||||
|
A veces es útil reducir la definición de la imagen del dispositivo Android para aumentar el desempeño.
|
||||||
|
|
||||||
|
Para limitar el ancho y la altura a un valor específico (ej. 1024):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --max-size 1024
|
||||||
|
scrcpy -m 1024 # versión breve
|
||||||
|
```
|
||||||
|
|
||||||
|
La otra dimensión es calculada para conservar el aspect ratio del dispositivo.
|
||||||
|
De esta forma, un dispositivo en 1920×1080 será transmitido a 1024×576.
|
||||||
|
|
||||||
|
|
||||||
|
#### Cambiar el bit-rate
|
||||||
|
|
||||||
|
El bit-rate por defecto es 8 Mbps. Para cambiar el bit-rate del video (ej. a 2 Mbps):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --bit-rate 2M
|
||||||
|
scrcpy -b 2M # versión breve
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Limitar los fps
|
||||||
|
|
||||||
|
El fps puede ser limitado:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --max-fps 15
|
||||||
|
```
|
||||||
|
|
||||||
|
Es oficialmente soportado desde Android 10, pero puede funcionar en versiones anteriores.
|
||||||
|
|
||||||
|
#### Recortar
|
||||||
|
|
||||||
|
La imagen del dispositivo puede ser recortada para transmitir solo una parte de la pantalla.
|
||||||
|
|
||||||
|
Por ejemplo, puede ser útil para transmitir la imagen de un solo ojo del Oculus Go:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --crop 1224:1440:0:0 # 1224x1440 con coordenadas de origen en (0,0)
|
||||||
|
```
|
||||||
|
|
||||||
|
Si `--max-size` también está especificado, el cambio de tamaño es aplicado después de cortar.
|
||||||
|
|
||||||
|
|
||||||
|
#### Fijar la rotación del video
|
||||||
|
|
||||||
|
|
||||||
|
Para fijar la rotación de la transmisión:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --lock-video-orientation 0 # orientación normal
|
||||||
|
scrcpy --lock-video-orientation 1 # 90° contrarreloj
|
||||||
|
scrcpy --lock-video-orientation 2 # 180°
|
||||||
|
scrcpy --lock-video-orientation 3 # 90° sentido de las agujas del reloj
|
||||||
|
```
|
||||||
|
|
||||||
|
Esto afecta la rotación de la grabación.
|
||||||
|
|
||||||
|
La [ventana también puede ser rotada](#rotación) independientemente.
|
||||||
|
|
||||||
|
|
||||||
|
#### Codificador
|
||||||
|
|
||||||
|
Algunos dispositivos pueden tener más de una rotación, y algunos pueden causar problemas o errores. Es posible seleccionar un codificador diferente:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --encoder OMX.qcom.video.encoder.avc
|
||||||
|
```
|
||||||
|
|
||||||
|
Para listar los codificadores disponibles, puedes pasar un nombre de codificador inválido, el error te dará los codificadores disponibles:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --encoder _
|
||||||
|
```
|
||||||
|
|
||||||
|
### Grabación
|
||||||
|
|
||||||
|
Es posible grabar la pantalla mientras se transmite:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --record file.mp4
|
||||||
|
scrcpy -r file.mkv
|
||||||
|
```
|
||||||
|
|
||||||
|
Para grabar sin transmitir la pantalla:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-display --record file.mp4
|
||||||
|
scrcpy -Nr file.mkv
|
||||||
|
# interrumpe la grabación con Ctrl+C
|
||||||
|
```
|
||||||
|
|
||||||
|
"Skipped frames" son grabados, incluso si no son mostrados en tiempo real (por razones de desempeño). Los frames tienen _marcas de tiempo_ en el dispositivo, por lo que el "[packet delay
|
||||||
|
variation]" no impacta el archivo grabado.
|
||||||
|
|
||||||
|
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
||||||
|
|
||||||
|
|
||||||
|
### Conexión
|
||||||
|
|
||||||
|
#### Inalámbrica
|
||||||
|
|
||||||
|
_Scrcpy_ usa `adb` para comunicarse con el dispositivo, y `adb` puede [conectarse] vía TCP/IP:
|
||||||
|
|
||||||
|
1. Conecta el dispositivo al mismo Wi-Fi que tu computadora.
|
||||||
|
2. Obtén la dirección IP del dispositivo, en Ajustes → Acerca del dispositivo → Estado, o ejecutando este comando:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb shell ip route | awk '{print $9}'
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Habilita adb vía TCP/IP en el dispositivo: `adb tcpip 5555`.
|
||||||
|
4. Desenchufa el dispositivo.
|
||||||
|
5. Conéctate a tu dispositivo: `adb connect IP_DEL_DISPOSITIVO:5555` _(reemplaza `IP_DEL_DISPOSITIVO`)_.
|
||||||
|
6. Ejecuta `scrcpy` con normalidad.
|
||||||
|
|
||||||
|
Podría resultar útil reducir el bit-rate y la definición:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --bit-rate 2M --max-size 800
|
||||||
|
scrcpy -b2M -m800 # versión breve
|
||||||
|
```
|
||||||
|
|
||||||
|
[conectarse]: https://developer.android.com/studio/command-line/adb.html#wireless
|
||||||
|
|
||||||
|
|
||||||
|
#### Múltiples dispositivos
|
||||||
|
|
||||||
|
Si hay muchos dispositivos listados en `adb devices`, será necesario especificar el _número de serie_:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --serial 0123456789abcdef
|
||||||
|
scrcpy -s 0123456789abcdef # versión breve
|
||||||
|
```
|
||||||
|
|
||||||
|
Si el dispositivo está conectado por TCP/IP:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --serial 192.168.0.1:5555
|
||||||
|
scrcpy -s 192.168.0.1:5555 # versión breve
|
||||||
|
```
|
||||||
|
|
||||||
|
Puedes iniciar múltiples instancias de _scrcpy_ para múltiples dispositivos.
|
||||||
|
|
||||||
|
#### Autoiniciar al detectar dispositivo
|
||||||
|
|
||||||
|
Puedes utilizar [AutoAdb]:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
autoadb scrcpy -s '{}'
|
||||||
|
```
|
||||||
|
|
||||||
|
[AutoAdb]: https://github.com/rom1v/autoadb
|
||||||
|
|
||||||
|
#### Túnel SSH
|
||||||
|
|
||||||
|
Para conectarse a un dispositivo remoto, es posible conectar un cliente local de `adb` a un servidor remoto `adb` (siempre y cuando utilicen la misma versión de protocolos _adb_):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb kill-server # cierra el servidor local adb en 5037
|
||||||
|
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
|
||||||
|
# conserva este servidor abierto
|
||||||
|
```
|
||||||
|
|
||||||
|
Desde otra terminal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
Para evitar habilitar "remote port forwarding", puedes forzar una "forward connection" (nótese el argumento `-L` en vez de `-R`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb kill-server # cierra el servidor local adb en 5037
|
||||||
|
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
|
||||||
|
# conserva este servidor abierto
|
||||||
|
```
|
||||||
|
|
||||||
|
Desde otra terminal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --force-adb-forward
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Al igual que las conexiones inalámbricas, puede resultar útil reducir la calidad:
|
||||||
|
|
||||||
|
```
|
||||||
|
scrcpy -b2M -m800 --max-fps 15
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuración de la ventana
|
||||||
|
|
||||||
|
#### Título
|
||||||
|
|
||||||
|
Por defecto, el título de la ventana es el modelo del dispositivo. Puede ser modificado:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --window-title 'My device'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Posición y tamaño
|
||||||
|
|
||||||
|
La posición y tamaño inicial de la ventana puede ser especificado:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --window-x 100 --window-y 100 --window-width 800 --window-height 600
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Sin bordes
|
||||||
|
|
||||||
|
Para deshabilitar el diseño de la ventana:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --window-borderless
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Siempre adelante
|
||||||
|
|
||||||
|
Para mantener la ventana de scrcpy siempre adelante:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --always-on-top
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Pantalla completa
|
||||||
|
|
||||||
|
La aplicación puede ser iniciada en pantalla completa:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --fullscreen
|
||||||
|
scrcpy -f # versión breve
|
||||||
|
```
|
||||||
|
|
||||||
|
Puede entrar y salir de la pantalla completa con la combinación <kbd>MOD</kbd>+<kbd>f</kbd>.
|
||||||
|
|
||||||
|
#### Rotación
|
||||||
|
|
||||||
|
Se puede rotar la ventana:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --rotation 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Los valores posibles son:
|
||||||
|
- `0`: sin rotación
|
||||||
|
- `1`: 90 grados contrarreloj
|
||||||
|
- `2`: 180 grados
|
||||||
|
- `3`: 90 grados en sentido de las agujas del reloj
|
||||||
|
|
||||||
|
La rotación también puede ser modificada con la combinación de teclas <kbd>MOD</kbd>+<kbd>←</kbd> _(izquierda)_ y <kbd>MOD</kbd>+<kbd>→</kbd> _(derecha)_.
|
||||||
|
|
||||||
|
Nótese que _scrcpy_ maneja 3 diferentes rotaciones:
|
||||||
|
- <kbd>MOD</kbd>+<kbd>r</kbd> solicita al dispositivo cambiar entre vertical y horizontal (la aplicación en uso puede rechazarlo si no soporta la orientación solicitada).
|
||||||
|
- [`--lock-video-orientation`](#fijar-la-rotación-del-video) cambia la rotación de la transmisión (la orientación del video enviado a la PC). Esto afecta a la grabación.
|
||||||
|
- `--rotation` (o <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>) rota solo el contenido de la imagen. Esto solo afecta a la imagen mostrada, no a la grabación.
|
||||||
|
|
||||||
|
|
||||||
|
### Otras opciones menores
|
||||||
|
|
||||||
|
#### Solo lectura ("Read-only")
|
||||||
|
|
||||||
|
Para deshabilitar los controles (todo lo que interactúe con el dispositivo: eventos del teclado, eventos del mouse, arrastrar y soltar archivos):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-control
|
||||||
|
scrcpy -n # versión breve
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Pantalla
|
||||||
|
|
||||||
|
Si múltiples pantallas están disponibles, es posible elegir cual transmitir:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --display 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Los ids de las pantallas se pueden obtener con el siguiente comando:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb shell dumpsys display # busque "mDisplayId=" en la respuesta
|
||||||
|
```
|
||||||
|
|
||||||
|
La segunda pantalla solo puede ser manejada si el dispositivo cuenta con Android 10 (en caso contrario será transmitida en el modo solo lectura).
|
||||||
|
|
||||||
|
|
||||||
|
#### Permanecer activo
|
||||||
|
|
||||||
|
Para evitar que el dispositivo descanse después de un tiempo mientras está conectado:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --stay-awake
|
||||||
|
scrcpy -w # versión breve
|
||||||
|
```
|
||||||
|
|
||||||
|
La configuración original se restaura al cerrar scrcpy.
|
||||||
|
|
||||||
|
|
||||||
|
#### Apagar la pantalla
|
||||||
|
|
||||||
|
Es posible apagar la pantalla mientras se transmite al iniciar con el siguiente comando:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --turn-screen-off
|
||||||
|
scrcpy -S # versión breve
|
||||||
|
```
|
||||||
|
|
||||||
|
O presionando <kbd>MOD</kbd>+<kbd>o</kbd> en cualquier momento.
|
||||||
|
|
||||||
|
Para volver a prenderla, presione <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>.
|
||||||
|
|
||||||
|
En Android, el botón de `POWER` siempre prende la pantalla. Por conveniencia, si `POWER` es enviado vía scrcpy (con click-derecho o <kbd>MOD</kbd>+<kbd>p</kbd>), esto forzará a apagar la pantalla con un poco de atraso (en la mejor de las situaciones). El botón físico `POWER` seguirá prendiendo la pantalla.
|
||||||
|
|
||||||
|
También puede resultar útil para evitar que el dispositivo entre en inactividad:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --turn-screen-off --stay-awake
|
||||||
|
scrcpy -Sw # versión breve
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Renderizar frames vencidos
|
||||||
|
|
||||||
|
Por defecto, para minimizar la latencia, _scrcpy_ siempre renderiza el último frame disponible decodificado, e ignora cualquier frame anterior.
|
||||||
|
|
||||||
|
Para forzar el renderizado de todos los frames (a costo de posible aumento de latencia), use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --render-expired-frames
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Mostrar clicks
|
||||||
|
|
||||||
|
Para presentaciones, puede resultar útil mostrar los clicks físicos (en el dispositivo físicamente).
|
||||||
|
|
||||||
|
Android provee esta opción en _Opciones para desarrolladores_.
|
||||||
|
|
||||||
|
_Scrcpy_ provee una opción para habilitar esta función al iniciar la aplicación y restaurar el valor original al salir:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --show-touches
|
||||||
|
scrcpy -t # versión breve
|
||||||
|
```
|
||||||
|
|
||||||
|
Nótese que solo muestra los clicks _físicos_ (con el dedo en el dispositivo).
|
||||||
|
|
||||||
|
|
||||||
|
#### Desactivar protector de pantalla
|
||||||
|
|
||||||
|
Por defecto, scrcpy no evita que el protector de pantalla se active en la computadora.
|
||||||
|
|
||||||
|
Para deshabilitarlo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --disable-screensaver
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Control
|
||||||
|
|
||||||
|
#### Rotar pantalla del dispositivo
|
||||||
|
|
||||||
|
Presione <kbd>MOD</kbd>+<kbd>r</kbd> para cambiar entre posición vertical y horizontal.
|
||||||
|
|
||||||
|
Nótese que solo rotará si la aplicación activa soporta la orientación solicitada.
|
||||||
|
|
||||||
|
#### Copiar y pegar
|
||||||
|
|
||||||
|
Cuando que el portapapeles de Android cambia, automáticamente se sincroniza al portapapeles de la computadora.
|
||||||
|
|
||||||
|
Cualquier shortcut con <kbd>Ctrl</kbd> es enviado al dispositivo. En particular:
|
||||||
|
- <kbd>Ctrl</kbd>+<kbd>c</kbd> normalmente copia
|
||||||
|
- <kbd>Ctrl</kbd>+<kbd>x</kbd> normalmente corta
|
||||||
|
- <kbd>Ctrl</kbd>+<kbd>v</kbd> normalmente pega (después de la sincronización de portapapeles entre la computadora y el dispositivo)
|
||||||
|
|
||||||
|
Esto normalmente funciona como es esperado.
|
||||||
|
|
||||||
|
Sin embargo, este comportamiento depende de la aplicación en uso. Por ejemplo, _Termux_ envía SIGINT con <kbd>Ctrl</kbd>+<kbd>c</kbd>, y _K-9 Mail_ crea un nuevo mensaje.
|
||||||
|
|
||||||
|
Para copiar, cortar y pegar, en tales casos (solo soportado en Android >= 7):
|
||||||
|
- <kbd>MOD</kbd>+<kbd>c</kbd> inyecta `COPY`
|
||||||
|
- <kbd>MOD</kbd>+<kbd>x</kbd> inyecta `CUT`
|
||||||
|
- <kbd>MOD</kbd>+<kbd>v</kbd> inyecta `PASTE` (después de la sincronización de portapapeles entre la computadora y el dispositivo)
|
||||||
|
|
||||||
|
Además, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> permite inyectar el texto en el portapapeles de la computadora como una secuencia de teclas. Esto es útil cuando el componente no acepta pegado de texto (por ejemplo en _Termux_), pero puede romper caracteres no pertenecientes a ASCII.
|
||||||
|
|
||||||
|
**AVISO:** Pegar de la computadora al dispositivo (tanto con <kbd>Ctrl</kbd>+<kbd>v</kbd> o <kbd>MOD</kbd>+<kbd>v</kbd>) copia el contenido al portapapeles del dispositivo. Como consecuencia, cualquier aplicación de Android puede leer su contenido. Debería evitar pegar contenido sensible (como contraseñas) de esta forma.
|
||||||
|
|
||||||
|
Algunos dispositivos no se comportan como es esperado al establecer el portapapeles programáticamente. La opción `--legacy-paste` está disponible para cambiar el comportamiento de <kbd>Ctrl</kbd>+<kbd>v</kbd> y <kbd>MOD</kbd>+<kbd>v</kbd> para que también inyecten el texto del portapapeles de la computadora como una secuencia de teclas (de la misma forma que <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
|
||||||
|
|
||||||
|
#### Pellizcar para zoom
|
||||||
|
|
||||||
|
Para simular "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-y-mover_.
|
||||||
|
|
||||||
|
Más precisamente, mantén <kbd>Ctrl</kbd> mientras presionas botón izquierdo. Hasta que no se suelte el botón, todos los movimientos del mouse cambiarán el tamaño y rotación del contenido (si es soportado por la app en uso) respecto al centro de la pantalla.
|
||||||
|
|
||||||
|
Concretamente, scrcpy genera clicks adicionales con un "dedo virtual" en la posición invertida respecto al centro de la pantalla.
|
||||||
|
|
||||||
|
|
||||||
|
#### Preferencias de inyección de texto
|
||||||
|
|
||||||
|
Existen dos tipos de [eventos][textevents] generados al escribir texto:
|
||||||
|
- _key events_, marcando si la tecla es presionada o soltada;
|
||||||
|
- _text events_, marcando si un texto fue introducido.
|
||||||
|
|
||||||
|
Por defecto, las letras son inyectadas usando _key events_, para que el teclado funcione como es esperado en juegos (típicamente las teclas WASD).
|
||||||
|
|
||||||
|
Pero esto puede [causar problemas][prefertext]. Si encuentras tales problemas, los puedes evitar con:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --prefer-text
|
||||||
|
```
|
||||||
|
|
||||||
|
(Pero esto romperá el comportamiento del teclado en los juegos)
|
||||||
|
|
||||||
|
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
||||||
|
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
||||||
|
|
||||||
|
|
||||||
|
#### Repetir tecla
|
||||||
|
|
||||||
|
Por defecto, mantener una tecla presionada genera múltiples _key events_. Esto puede causar problemas de desempeño en algunos juegos, donde estos eventos no tienen sentido de todos modos.
|
||||||
|
|
||||||
|
Para evitar enviar _key events_ repetidos:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-key-repeat
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Botón derecho y botón del medio
|
||||||
|
|
||||||
|
Por defecto, botón derecho ejecuta RETROCEDER (o ENCENDIDO) y botón del medio INICIO. Para inhabilitar estos atajos y enviar los clicks al dispositivo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --forward-all-clicks
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Arrastrar y soltar archivos
|
||||||
|
|
||||||
|
#### Instalar APKs
|
||||||
|
|
||||||
|
Para instalar un APK, arrastre y suelte el archivo APK (terminado en `.apk`) a la ventana de _scrcpy_.
|
||||||
|
|
||||||
|
No hay respuesta visual, un mensaje se escribirá en la consola.
|
||||||
|
|
||||||
|
|
||||||
|
#### Enviar archivos al dispositivo
|
||||||
|
|
||||||
|
Para enviar un archivo a `/sdcard/` en el dispositivo, arrastre y suelte un archivo (no APK) a la ventana de _scrcpy_.
|
||||||
|
|
||||||
|
No hay respuesta visual, un mensaje se escribirá en la consola.
|
||||||
|
|
||||||
|
El directorio de destino puede ser modificado al iniciar:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --push-target=/sdcard/Download/
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Envío de Audio
|
||||||
|
|
||||||
|
_Scrcpy_ no envía el audio. Use [sndcpy].
|
||||||
|
|
||||||
|
También lea [issue #14].
|
||||||
|
|
||||||
|
[sndcpy]: https://github.com/rom1v/sndcpy
|
||||||
|
[issue #14]: https://github.com/Genymobile/scrcpy/issues/14
|
||||||
|
|
||||||
|
|
||||||
|
## Atajos
|
||||||
|
|
||||||
|
En la siguiente lista, <kbd>MOD</kbd> es el atajo modificador. Por defecto es <kbd>Alt</kbd> (izquierdo) o <kbd>Super</kbd> (izquierdo).
|
||||||
|
|
||||||
|
Se puede modificar usando `--shortcut-mod`. Las posibles teclas son `lctrl` (izquierdo), `rctrl` (derecho), `lalt` (izquierdo), `ralt` (derecho), `lsuper` (izquierdo) y `rsuper` (derecho). Por ejemplo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# use RCtrl para los atajos
|
||||||
|
scrcpy --shortcut-mod=rctrl
|
||||||
|
|
||||||
|
# use tanto LCtrl+LAlt o LSuper para los atajos
|
||||||
|
scrcpy --shortcut-mod=lctrl+lalt,lsuper
|
||||||
|
```
|
||||||
|
|
||||||
|
_<kbd>[Super]</kbd> es generalmente la tecla <kbd>Windows</kbd> o <kbd>Cmd</kbd>._
|
||||||
|
|
||||||
|
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
|
||||||
|
|
||||||
|
| Acción | Atajo
|
||||||
|
| ------------------------------------------- |:-----------------------------
|
||||||
|
| Alterne entre pantalla compelta | <kbd>MOD</kbd>+<kbd>f</kbd>
|
||||||
|
| Rotar pantalla hacia la izquierda | <kbd>MOD</kbd>+<kbd>←</kbd> _(izquierda)_
|
||||||
|
| Rotar pantalla hacia la derecha | <kbd>MOD</kbd>+<kbd>→</kbd> _(derecha)_
|
||||||
|
| Ajustar ventana a 1:1 ("pixel-perfect") | <kbd>MOD</kbd>+<kbd>g</kbd>
|
||||||
|
| Ajustar ventana para quitar los bordes negros| <kbd>MOD</kbd>+<kbd>w</kbd> \| _Doble click¹_
|
||||||
|
| Click en `INICIO` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Botón del medio_
|
||||||
|
| Click en `RETROCEDER` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Botón derecho²_
|
||||||
|
| Click en `CAMBIAR APLICACIÓN` | <kbd>MOD</kbd>+<kbd>s</kbd>
|
||||||
|
| Click en `MENÚ` (desbloquear pantalla) | <kbd>MOD</kbd>+<kbd>m</kbd>
|
||||||
|
| Click en `SUBIR VOLUMEN` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(arriba)_
|
||||||
|
| Click en `BAJAR VOLUME` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(abajo)_
|
||||||
|
| Click en `ENCENDIDO` | <kbd>MOD</kbd>+<kbd>p</kbd>
|
||||||
|
| Encendido | _Botón derecho²_
|
||||||
|
| Apagar pantalla (manteniendo la transmisión)| <kbd>MOD</kbd>+<kbd>o</kbd>
|
||||||
|
| Encender pantalla | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
||||||
|
| Rotar pantalla del dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
|
||||||
|
| Abrir panel de notificaciones | <kbd>MOD</kbd>+<kbd>n</kbd>
|
||||||
|
| Cerrar panel de notificaciones | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||||
|
| Copiar al portapapeles³ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||||
|
| Cortar al portapapeles³ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||||
|
| Synchronizar portapapeles y pegar³ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||||
|
| inyectar texto del portapapeles de la PC | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
||||||
|
| Habilitar/Deshabilitar contador de FPS (en stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||||
|
| Pellizcar para zoom | <kbd>Ctrl</kbd>+_click-y-mover_
|
||||||
|
|
||||||
|
_¹Doble click en los bordes negros para eliminarlos._
|
||||||
|
_²Botón derecho enciende la pantalla si estaba apagada, sino ejecuta RETROCEDER._
|
||||||
|
_³Solo en Android >= 7._
|
||||||
|
|
||||||
|
Todos los atajos <kbd>Ctrl</kbd>+_tecla_ son enviados al dispositivo para que sean manejados por la aplicación activa.
|
||||||
|
|
||||||
|
|
||||||
|
## Path personalizado
|
||||||
|
|
||||||
|
Para usar un binario de _adb_ en particular, configure el path `ADB` en las variables de entorno:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ADB=/path/to/adb scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
Para sobreescribir el path del archivo `scrcpy-server`, configure el path en `SCRCPY_SERVER_PATH`.
|
||||||
|
|
||||||
|
|
||||||
|
## ¿Por qué _scrcpy_?
|
||||||
|
|
||||||
|
Un colega me retó a encontrar un nombre tan impronunciable como [gnirehtet].
|
||||||
|
|
||||||
|
[`strcpy`] copia un **str**ing; `scrcpy` copia un **scr**een.
|
||||||
|
|
||||||
|
[gnirehtet]: https://github.com/Genymobile/gnirehtet
|
||||||
|
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html
|
||||||
|
|
||||||
|
|
||||||
|
## ¿Cómo construir (BUILD)?
|
||||||
|
|
||||||
|
Véase [BUILD] (en inglés).
|
||||||
|
|
||||||
|
|
||||||
|
## Problemas generales
|
||||||
|
|
||||||
|
Vea las [preguntas frecuentes (en inglés)](FAQ.md).
|
||||||
|
|
||||||
|
|
||||||
|
## Desarrolladores
|
||||||
|
|
||||||
|
Lea la [hoja de desarrolladores (en inglés)](DEVELOP.md).
|
||||||
|
|
||||||
|
|
||||||
|
## Licencia
|
||||||
|
|
||||||
|
Copyright (C) 2018 Genymobile
|
||||||
|
Copyright (C) 2018-2021 Romain Vimont
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
## Artículos
|
||||||
|
|
||||||
|
- [Introducing scrcpy][article-intro] (en inglés)
|
||||||
|
- [Scrcpy now works wirelessly][article-tcpip] (en inglés)
|
||||||
|
|
||||||
|
[article-intro]: https://blog.rom1v.com/2018/03/introducing-scrcpy/
|
||||||
|
[article-tcpip]: https://www.genymotion.com/blog/open-source-project-scrcpy-now-works-wirelessly/
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
src = [
|
src = [
|
||||||
'src/main.c',
|
'src/main.c',
|
||||||
|
'src/adb.c',
|
||||||
'src/cli.c',
|
'src/cli.c',
|
||||||
'src/command.c',
|
'src/compat.c',
|
||||||
'src/control_msg.c',
|
'src/control_msg.c',
|
||||||
'src/controller.c',
|
'src/controller.c',
|
||||||
'src/decoder.c',
|
'src/decoder.c',
|
||||||
'src/device.c',
|
|
||||||
'src/device_msg.c',
|
'src/device_msg.c',
|
||||||
'src/event_converter.c',
|
'src/event_converter.c',
|
||||||
'src/file_handler.c',
|
'src/file_handler.c',
|
||||||
@@ -20,10 +20,30 @@ src = [
|
|||||||
'src/stream.c',
|
'src/stream.c',
|
||||||
'src/tiny_xpm.c',
|
'src/tiny_xpm.c',
|
||||||
'src/video_buffer.c',
|
'src/video_buffer.c',
|
||||||
|
'src/util/log.c',
|
||||||
'src/util/net.c',
|
'src/util/net.c',
|
||||||
'src/util/str_util.c'
|
'src/util/process.c',
|
||||||
|
'src/util/str_util.c',
|
||||||
|
'src/util/thread.c',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if host_machine.system() == 'windows'
|
||||||
|
src += [ 'src/sys/win/process.c' ]
|
||||||
|
else
|
||||||
|
src += [ 'src/sys/unix/process.c' ]
|
||||||
|
endif
|
||||||
|
|
||||||
|
v4l2_support = host_machine.system() == 'linux'
|
||||||
|
if v4l2_support
|
||||||
|
src += [ 'src/v4l2_sink.c' ]
|
||||||
|
endif
|
||||||
|
|
||||||
|
check_functions = [
|
||||||
|
'strdup'
|
||||||
|
]
|
||||||
|
|
||||||
|
cc = meson.get_compiler('c')
|
||||||
|
|
||||||
if not get_option('crossbuild_windows')
|
if not get_option('crossbuild_windows')
|
||||||
|
|
||||||
# native build
|
# native build
|
||||||
@@ -34,11 +54,13 @@ if not get_option('crossbuild_windows')
|
|||||||
dependency('sdl2'),
|
dependency('sdl2'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if v4l2_support
|
||||||
|
dependencies += dependency('libavdevice')
|
||||||
|
endif
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
# cross-compile mingw32 build (from Linux to Windows)
|
# cross-compile mingw32 build (from Linux to Windows)
|
||||||
cc = meson.get_compiler('c')
|
|
||||||
|
|
||||||
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
|
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
|
||||||
sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin'
|
sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin'
|
||||||
sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/lib'
|
sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/lib'
|
||||||
@@ -73,19 +95,18 @@ else
|
|||||||
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
cc = meson.get_compiler('c')
|
|
||||||
|
|
||||||
if host_machine.system() == 'windows'
|
if host_machine.system() == 'windows'
|
||||||
src += [ 'src/sys/win/command.c' ]
|
|
||||||
dependencies += cc.find_library('ws2_32')
|
dependencies += cc.find_library('ws2_32')
|
||||||
else
|
|
||||||
src += [ 'src/sys/unix/command.c' ]
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
conf = configuration_data()
|
conf = configuration_data()
|
||||||
|
|
||||||
# expose the build type
|
foreach f : check_functions
|
||||||
conf.set('NDEBUG', get_option('buildtype') != 'debug')
|
if cc.has_function(f)
|
||||||
|
define = 'HAVE_' + f.underscorify().to_upper()
|
||||||
|
conf.set(define, true)
|
||||||
|
endif
|
||||||
|
endforeach
|
||||||
|
|
||||||
# the version, updated on release
|
# the version, updated on release
|
||||||
conf.set_quoted('SCRCPY_VERSION', meson.project_version())
|
conf.set_quoted('SCRCPY_VERSION', meson.project_version())
|
||||||
@@ -102,29 +123,19 @@ conf.set('PORTABLE', get_option('portable'))
|
|||||||
conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183')
|
conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183')
|
||||||
conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
|
conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
|
||||||
|
|
||||||
# the default max video size for both dimensions, in pixels
|
|
||||||
# overridden by option --max-size
|
|
||||||
conf.set('DEFAULT_MAX_SIZE', '0') # 0: unlimited
|
|
||||||
|
|
||||||
# the default video orientation
|
|
||||||
# natural device orientation is 0 and each increment adds 90 degrees
|
|
||||||
# counterclockwise
|
|
||||||
# overridden by option --lock-video-orientation
|
|
||||||
conf.set('DEFAULT_LOCK_VIDEO_ORIENTATION', '-1') # -1: unlocked
|
|
||||||
|
|
||||||
# the default video bitrate, in bits/second
|
# the default video bitrate, in bits/second
|
||||||
# overridden by option --bit-rate
|
# overridden by option --bit-rate
|
||||||
conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps
|
conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps
|
||||||
|
|
||||||
# enable High DPI support
|
|
||||||
conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
|
|
||||||
|
|
||||||
# run a server debugger and wait for a client to be attached
|
# run a server debugger and wait for a client to be attached
|
||||||
conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
|
conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
|
||||||
|
|
||||||
# select the debugger method ('old' for Android < 9, 'new' for Android >= 9)
|
# select the debugger method ('old' for Android < 9, 'new' for Android >= 9)
|
||||||
conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == 'new')
|
conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == 'new')
|
||||||
|
|
||||||
|
# enable V4L2 support (linux only)
|
||||||
|
conf.set('HAVE_V4L2', v4l2_support)
|
||||||
|
|
||||||
configure_file(configuration: conf, output: 'config.h')
|
configure_file(configuration: conf, output: 'config.h')
|
||||||
|
|
||||||
src_dir = include_directories('src')
|
src_dir = include_directories('src')
|
||||||
|
|||||||
26
app/scrcpy.1
26
app/scrcpy.1
@@ -83,10 +83,12 @@ Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+S
|
|||||||
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
|
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-lock\-video\-orientation " value
|
.BI "\-\-lock\-video\-orientation " [value]
|
||||||
Lock video orientation to \fIvalue\fR. Possible values are -1 (unlocked), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees otation counterclockwise.
|
Lock video orientation to \fIvalue\fR. Possible values are "unlocked", "initial" (locked to the initial orientation), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees otation counterclockwise.
|
||||||
|
|
||||||
Default is -1 (unlocked).
|
Default is "unlocked".
|
||||||
|
|
||||||
|
Passing the option without argument is equivalent to passing "initial".
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-max\-fps " value
|
.BI "\-\-max\-fps " value
|
||||||
@@ -131,7 +133,7 @@ but breaks the expected behavior of alpha keys in games (typically WASD).
|
|||||||
.BI "\-\-push\-target " path
|
.BI "\-\-push\-target " path
|
||||||
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".
|
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".
|
||||||
|
|
||||||
Default is "/sdcard/".
|
Default is "/sdcard/Download/".
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-r, \-\-record " file
|
.BI "\-r, \-\-record " file
|
||||||
@@ -155,10 +157,6 @@ Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "me
|
|||||||
.UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER
|
.UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER
|
||||||
.UE
|
.UE
|
||||||
|
|
||||||
.TP
|
|
||||||
.B \-\-render\-expired\-frames
|
|
||||||
By default, to minimize latency, scrcpy always renders the last available decoded frame, and drops any previous ones. This flag forces to render all frames, at a cost of a possible increased latency.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-rotation " value
|
.BI "\-\-rotation " value
|
||||||
Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each increment adds a 90 degrees rotation counterclockwise.
|
Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each increment adds a 90 degrees rotation counterclockwise.
|
||||||
@@ -188,15 +186,21 @@ Enable "show touches" on start, restore the initial value on exit.
|
|||||||
It only shows physical touches (not clicks from scrcpy).
|
It only shows physical touches (not clicks from scrcpy).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-v, \-\-version
|
.BI "\-\-v4l2-sink " /dev/videoN
|
||||||
Print the version of scrcpy.
|
Output to v4l2loopback device.
|
||||||
|
|
||||||
|
It requires to lock the video orientation (see --lock-video-orientation).
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-V, \-\-verbosity " value
|
.BI "\-V, \-\-verbosity " value
|
||||||
Set the log level ("debug", "info", "warn" or "error").
|
Set the log level ("verbose", "debug", "info", "warn" or "error").
|
||||||
|
|
||||||
Default is "info" for release builds, "debug" for debug builds.
|
Default is "info" for release builds, "debug" for debug builds.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-v, \-\-version
|
||||||
|
Print the version of scrcpy.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-w, \-\-stay-awake
|
.B \-w, \-\-stay-awake
|
||||||
Keep the device on while scrcpy is running, when the device is plugged in.
|
Keep the device on while scrcpy is running, when the device is plugged in.
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
#include "command.h"
|
#include "adb.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "common.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str_util.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
@@ -70,7 +68,7 @@ show_adb_installation_msg() {
|
|||||||
{"pacman", "pacman -S android-tools"},
|
{"pacman", "pacman -S android-tools"},
|
||||||
};
|
};
|
||||||
for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) {
|
for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) {
|
||||||
if (cmd_search(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;
|
||||||
}
|
}
|
||||||
@@ -83,14 +81,20 @@ show_adb_installation_msg() {
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
show_adb_err_msg(enum process_result err, const char *const argv[]) {
|
show_adb_err_msg(enum process_result err, const char *const argv[]) {
|
||||||
char buf[512];
|
#define MAX_COMMAND_STRING_LEN 1024
|
||||||
|
char *buf = malloc(MAX_COMMAND_STRING_LEN);
|
||||||
|
if (!buf) {
|
||||||
|
LOGE("Failed to execute (could not allocate error message)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (err) {
|
switch (err) {
|
||||||
case PROCESS_ERROR_GENERIC:
|
case PROCESS_ERROR_GENERIC:
|
||||||
argv_to_string(argv, buf, sizeof(buf));
|
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
|
||||||
LOGE("Failed to execute: %s", buf);
|
LOGE("Failed to execute: %s", buf);
|
||||||
break;
|
break;
|
||||||
case PROCESS_ERROR_MISSING_BINARY:
|
case PROCESS_ERROR_MISSING_BINARY:
|
||||||
argv_to_string(argv, buf, sizeof(buf));
|
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)");
|
||||||
@@ -100,29 +104,38 @@ show_adb_err_msg(enum process_result err, const char *const argv[]) {
|
|||||||
// do nothing
|
// do nothing
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
process_t
|
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) {
|
||||||
const char *cmd[len + 4];
|
|
||||||
int i;
|
int i;
|
||||||
process_t process;
|
process_t process;
|
||||||
cmd[0] = get_adb_command();
|
|
||||||
|
const char **argv = malloc((len + 4) * sizeof(*argv));
|
||||||
|
if (!argv) {
|
||||||
|
return PROCESS_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
argv[0] = get_adb_command();
|
||||||
if (serial) {
|
if (serial) {
|
||||||
cmd[1] = "-s";
|
argv[1] = "-s";
|
||||||
cmd[2] = serial;
|
argv[2] = serial;
|
||||||
i = 3;
|
i = 3;
|
||||||
} else {
|
} else {
|
||||||
i = 1;
|
i = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(&cmd[i], adb_cmd, len * sizeof(const char *));
|
memcpy(&argv[i], adb_cmd, len * sizeof(const char *));
|
||||||
cmd[len + i] = NULL;
|
argv[len + i] = NULL;
|
||||||
enum process_result r = cmd_execute(cmd, &process);
|
enum process_result r = process_execute(argv, &process);
|
||||||
if (r != PROCESS_SUCCESS) {
|
if (r != PROCESS_SUCCESS) {
|
||||||
show_adb_err_msg(r, cmd);
|
show_adb_err_msg(r, argv);
|
||||||
return PROCESS_NONE;
|
process = PROCESS_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(argv);
|
||||||
return process;
|
return process;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +188,7 @@ adb_push(const char *serial, const char *local, const char *remote) {
|
|||||||
}
|
}
|
||||||
remote = strquote(remote);
|
remote = strquote(remote);
|
||||||
if (!remote) {
|
if (!remote) {
|
||||||
SDL_free((void *) local);
|
free((void *) local);
|
||||||
return PROCESS_NONE;
|
return PROCESS_NONE;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -184,8 +197,8 @@ adb_push(const char *serial, const char *local, const char *remote) {
|
|||||||
process_t proc = 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__
|
||||||
SDL_free((void *) remote);
|
free((void *) remote);
|
||||||
SDL_free((void *) local);
|
free((void *) local);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return proc;
|
return proc;
|
||||||
@@ -206,26 +219,8 @@ adb_install(const char *serial, const char *local) {
|
|||||||
process_t proc = 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__
|
||||||
SDL_free((void *) local);
|
free((void *) local);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return proc;
|
return proc;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
process_check_success(process_t proc, const char *name) {
|
|
||||||
if (proc == PROCESS_NONE) {
|
|
||||||
LOGE("Could not execute \"%s\"", name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
exit_code_t exit_code;
|
|
||||||
if (!cmd_simple_wait(proc, &exit_code)) {
|
|
||||||
if (exit_code != NO_EXIT_CODE) {
|
|
||||||
LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code);
|
|
||||||
} else {
|
|
||||||
LOGE("\"%s\" exited unexpectedly", name);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
34
app/src/adb.h
Normal file
34
app/src/adb.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#ifndef SC_ADB_H
|
||||||
|
#define SC_ADB_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "util/process.h"
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_forward(const char *serial, uint16_t local_port,
|
||||||
|
const char *device_socket_name);
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_forward_remove(const char *serial, uint16_t local_port);
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_reverse(const char *serial, const char *device_socket_name,
|
||||||
|
uint16_t local_port);
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_reverse_remove(const char *serial, const char *device_socket_name);
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_push(const char *serial, const char *local, const char *remote);
|
||||||
|
|
||||||
|
process_t
|
||||||
|
adb_install(const char *serial, const char *local);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
#define _ANDROID_INPUT_H
|
#define _ANDROID_INPUT_H
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Meta key / modifer state.
|
* Meta key / modifier state.
|
||||||
*/
|
*/
|
||||||
enum android_metastate {
|
enum android_metastate {
|
||||||
/** No meta keys are pressed. */
|
/** No meta keys are pressed. */
|
||||||
|
|||||||
120
app/src/cli.c
120
app/src/cli.c
@@ -6,11 +6,13 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "scrcpy.h"
|
#include "scrcpy.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str_util.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
|
#define STR_IMPL_(x) #x
|
||||||
|
#define STR(x) STR_IMPL_(x)
|
||||||
|
|
||||||
void
|
void
|
||||||
scrcpy_print_usage(const char *arg0) {
|
scrcpy_print_usage(const char *arg0) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
@@ -24,7 +26,7 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" -b, --bit-rate value\n"
|
" -b, --bit-rate value\n"
|
||||||
" Encode the video at the given bit-rate, expressed in bits/s.\n"
|
" Encode the video at the given bit-rate, expressed in bits/s.\n"
|
||||||
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
|
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
|
||||||
" Default is %d.\n"
|
" Default is " STR(DEFAULT_BIT_RATE) ".\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --codec-options key[:type]=value[,...]\n"
|
" --codec-options key[:type]=value[,...]\n"
|
||||||
" Set a list of comma-separated key:type=value options for the\n"
|
" Set a list of comma-separated key:type=value options for the\n"
|
||||||
@@ -77,12 +79,15 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" This is a workaround for some devices not behaving as\n"
|
" This is a workaround for some devices not behaving as\n"
|
||||||
" expected when setting the device clipboard programmatically.\n"
|
" expected when setting the device clipboard programmatically.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --lock-video-orientation value\n"
|
" --lock-video-orientation [value]\n"
|
||||||
" Lock video orientation to value.\n"
|
" Lock video orientation to value.\n"
|
||||||
" Possible values are -1 (unlocked), 0, 1, 2 and 3.\n"
|
" Possible values are \"unlocked\", \"initial\" (locked to the\n"
|
||||||
|
" initial orientation), 0, 1, 2 and 3.\n"
|
||||||
" Natural device orientation is 0, and each increment adds a\n"
|
" Natural device orientation is 0, and each increment adds a\n"
|
||||||
" 90 degrees rotation counterclockwise.\n"
|
" 90 degrees rotation counterclockwise.\n"
|
||||||
" Default is %d%s.\n"
|
" Default is \"unlocked\".\n"
|
||||||
|
" Passing the option without argument is equivalent to passing\n"
|
||||||
|
" \"initial\".\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --max-fps value\n"
|
" --max-fps value\n"
|
||||||
" Limit the frame rate of screen capture (officially supported\n"
|
" Limit the frame rate of screen capture (officially supported\n"
|
||||||
@@ -92,7 +97,7 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" Limit both the width and height of the video to value. The\n"
|
" Limit both the width and height of the video to value. The\n"
|
||||||
" other dimension is computed so that the device aspect-ratio\n"
|
" other dimension is computed so that the device aspect-ratio\n"
|
||||||
" is preserved.\n"
|
" is preserved.\n"
|
||||||
" Default is %d%s.\n"
|
" Default is 0 (unlimited).\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -n, --no-control\n"
|
" -n, --no-control\n"
|
||||||
" Disable device control (mirror the device in read-only).\n"
|
" Disable device control (mirror the device in read-only).\n"
|
||||||
@@ -111,7 +116,8 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
"\n"
|
"\n"
|
||||||
" -p, --port port[:port]\n"
|
" -p, --port port[:port]\n"
|
||||||
" Set the TCP port (range) used by the client to listen.\n"
|
" Set the TCP port (range) used by the client to listen.\n"
|
||||||
" Default is %d:%d.\n"
|
" Default is " STR(DEFAULT_LOCAL_PORT_RANGE_FIRST) ":"
|
||||||
|
STR(DEFAULT_LOCAL_PORT_RANGE_LAST) ".\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --prefer-text\n"
|
" --prefer-text\n"
|
||||||
" Inject alpha characters and space as text events instead of\n"
|
" Inject alpha characters and space as text events instead of\n"
|
||||||
@@ -123,7 +129,7 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" --push-target path\n"
|
" --push-target path\n"
|
||||||
" Set the target directory for pushing files to the device by\n"
|
" Set the target directory for pushing files to the device by\n"
|
||||||
" drag & drop. It is passed as-is to \"adb push\".\n"
|
" drag & drop. It is passed as-is to \"adb push\".\n"
|
||||||
" Default is \"/sdcard/\".\n"
|
" Default is \"/sdcard/Download/\".\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -r, --record file.mp4\n"
|
" -r, --record file.mp4\n"
|
||||||
" Record screen to file.\n"
|
" Record screen to file.\n"
|
||||||
@@ -140,12 +146,6 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" \"opengles2\", \"opengles\", \"metal\" and \"software\".\n"
|
" \"opengles2\", \"opengles\", \"metal\" and \"software\".\n"
|
||||||
" <https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER>\n"
|
" <https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER>\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --render-expired-frames\n"
|
|
||||||
" By default, to minimize latency, scrcpy always renders the\n"
|
|
||||||
" last available decoded frame, and drops any previous ones.\n"
|
|
||||||
" This flag forces to render all frames, at a cost of a\n"
|
|
||||||
" possible increased latency.\n"
|
|
||||||
"\n"
|
|
||||||
" --rotation value\n"
|
" --rotation value\n"
|
||||||
" Set the initial display rotation.\n"
|
" Set the initial display rotation.\n"
|
||||||
" Possibles values are 0, 1, 2 and 3. Each increment adds a 90\n"
|
" Possibles values are 0, 1, 2 and 3. Each increment adds a 90\n"
|
||||||
@@ -176,16 +176,23 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" on exit.\n"
|
" on exit.\n"
|
||||||
" It only shows physical touches (not clicks from scrcpy).\n"
|
" It only shows physical touches (not clicks from scrcpy).\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -v, --version\n"
|
#ifdef HAVE_V4L2
|
||||||
" Print the version of scrcpy.\n"
|
" --v4l2-sink /dev/videoN\n"
|
||||||
|
" Output to v4l2loopback device.\n"
|
||||||
|
" It requires to lock the video orientation (see\n"
|
||||||
|
" --lock-video-orientation).\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
#endif
|
||||||
" -V, --verbosity value\n"
|
" -V, --verbosity value\n"
|
||||||
" Set the log level (debug, info, warn or error).\n"
|
" Set the log level (verbose, debug, info, warn or error).\n"
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
" Default is debug.\n"
|
" Default is debug.\n"
|
||||||
#else
|
#else
|
||||||
" Default is info.\n"
|
" Default is info.\n"
|
||||||
#endif
|
#endif
|
||||||
|
"\n"
|
||||||
|
" -v, --version\n"
|
||||||
|
" Print the version of scrcpy.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -w, --stay-awake\n"
|
" -w, --stay-awake\n"
|
||||||
" Keep the device on while scrcpy is running, when the device\n"
|
" Keep the device on while scrcpy is running, when the device\n"
|
||||||
@@ -210,7 +217,7 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" Default is 0 (automatic).\n"
|
" Default is 0 (automatic).\n"
|
||||||
"\n"
|
"\n"
|
||||||
" --window-height value\n"
|
" --window-height value\n"
|
||||||
" Set the initial window width.\n"
|
" Set the initial window height.\n"
|
||||||
" Default is 0 (automatic).\n"
|
" Default is 0 (automatic).\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Shortcuts:\n"
|
"Shortcuts:\n"
|
||||||
@@ -298,12 +305,7 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
"\n"
|
"\n"
|
||||||
" Drag & drop APK file\n"
|
" Drag & drop APK file\n"
|
||||||
" Install APK from computer\n"
|
" Install APK from computer\n"
|
||||||
"\n",
|
"\n", arg0);
|
||||||
arg0,
|
|
||||||
DEFAULT_BIT_RATE,
|
|
||||||
DEFAULT_LOCK_VIDEO_ORIENTATION, DEFAULT_LOCK_VIDEO_ORIENTATION >= 0 ? "" : " (unlocked)",
|
|
||||||
DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
|
|
||||||
DEFAULT_LOCAL_PORT_RANGE_FIRST, DEFAULT_LOCAL_PORT_RANGE_LAST);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@@ -391,15 +393,27 @@ parse_max_fps(const char *s, uint16_t *max_fps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_lock_video_orientation(const char *s, int8_t *lock_video_orientation) {
|
parse_lock_video_orientation(const char *s,
|
||||||
|
enum sc_lock_video_orientation *lock_mode) {
|
||||||
|
if (!s || !strcmp(s, "initial")) {
|
||||||
|
// Without argument, lock the initial orientation
|
||||||
|
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(s, "unlocked")) {
|
||||||
|
*lock_mode = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
long value;
|
long value;
|
||||||
bool ok = parse_integer_arg(s, &value, false, -1, 3,
|
bool ok = parse_integer_arg(s, &value, false, 0, 3,
|
||||||
"lock video orientation");
|
"lock video orientation");
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*lock_video_orientation = (int8_t) value;
|
*lock_mode = (enum sc_lock_video_orientation) value;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,19 +492,24 @@ parse_port_range(const char *s, struct sc_port_range *port_range) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_display_id(const char *s, uint16_t *display_id) {
|
parse_display_id(const char *s, uint32_t *display_id) {
|
||||||
long value;
|
long value;
|
||||||
bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "display id");
|
bool ok = parse_integer_arg(s, &value, false, 0, 0x7FFFFFFF, "display id");
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*display_id = (uint16_t) value;
|
*display_id = (uint32_t) value;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
parse_log_level(const char *s, enum sc_log_level *log_level) {
|
parse_log_level(const char *s, enum sc_log_level *log_level) {
|
||||||
|
if (!strcmp(s, "verbose")) {
|
||||||
|
*log_level = SC_LOG_LEVEL_VERBOSE;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!strcmp(s, "debug")) {
|
if (!strcmp(s, "debug")) {
|
||||||
*log_level = SC_LOG_LEVEL_DEBUG;
|
*log_level = SC_LOG_LEVEL_DEBUG;
|
||||||
return true;
|
return true;
|
||||||
@@ -668,6 +687,8 @@ guess_record_format(const char *filename) {
|
|||||||
#define OPT_FORWARD_ALL_CLICKS 1023
|
#define OPT_FORWARD_ALL_CLICKS 1023
|
||||||
#define OPT_LEGACY_PASTE 1024
|
#define OPT_LEGACY_PASTE 1024
|
||||||
#define OPT_ENCODER_NAME 1025
|
#define OPT_ENCODER_NAME 1025
|
||||||
|
#define OPT_POWER_OFF_ON_CLOSE 1026
|
||||||
|
#define OPT_V4L2_SINK 1027
|
||||||
|
|
||||||
bool
|
bool
|
||||||
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
||||||
@@ -687,7 +708,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
{"fullscreen", no_argument, NULL, 'f'},
|
{"fullscreen", no_argument, NULL, 'f'},
|
||||||
{"help", no_argument, NULL, 'h'},
|
{"help", no_argument, NULL, 'h'},
|
||||||
{"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE},
|
{"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE},
|
||||||
{"lock-video-orientation", required_argument, NULL,
|
{"lock-video-orientation", optional_argument, NULL,
|
||||||
OPT_LOCK_VIDEO_ORIENTATION},
|
OPT_LOCK_VIDEO_ORIENTATION},
|
||||||
{"max-fps", required_argument, NULL, OPT_MAX_FPS},
|
{"max-fps", required_argument, NULL, OPT_MAX_FPS},
|
||||||
{"max-size", required_argument, NULL, 'm'},
|
{"max-size", required_argument, NULL, 'm'},
|
||||||
@@ -709,6 +730,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
{"show-touches", no_argument, NULL, 't'},
|
{"show-touches", no_argument, NULL, 't'},
|
||||||
{"stay-awake", no_argument, NULL, 'w'},
|
{"stay-awake", no_argument, NULL, 'w'},
|
||||||
{"turn-screen-off", no_argument, NULL, 'S'},
|
{"turn-screen-off", no_argument, NULL, 'S'},
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
{"v4l2-sink", required_argument, NULL, OPT_V4L2_SINK},
|
||||||
|
#endif
|
||||||
{"verbosity", required_argument, NULL, 'V'},
|
{"verbosity", required_argument, NULL, 'V'},
|
||||||
{"version", no_argument, NULL, 'v'},
|
{"version", no_argument, NULL, 'v'},
|
||||||
{"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
|
{"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
|
||||||
@@ -718,6 +742,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
{"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT},
|
{"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT},
|
||||||
{"window-borderless", no_argument, NULL,
|
{"window-borderless", no_argument, NULL,
|
||||||
OPT_WINDOW_BORDERLESS},
|
OPT_WINDOW_BORDERLESS},
|
||||||
|
{"power-off-on-close", no_argument, NULL,
|
||||||
|
OPT_POWER_OFF_ON_CLOSE},
|
||||||
{NULL, 0, NULL, 0 },
|
{NULL, 0, NULL, 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -770,7 +796,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case OPT_LOCK_VIDEO_ORIENTATION:
|
case OPT_LOCK_VIDEO_ORIENTATION:
|
||||||
if (!parse_lock_video_orientation(optarg, &opts->lock_video_orientation)) {
|
if (!parse_lock_video_orientation(optarg,
|
||||||
|
&opts->lock_video_orientation)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -815,7 +842,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
opts->stay_awake = true;
|
opts->stay_awake = true;
|
||||||
break;
|
break;
|
||||||
case OPT_RENDER_EXPIRED_FRAMES:
|
case OPT_RENDER_EXPIRED_FRAMES:
|
||||||
opts->render_expired_frames = true;
|
LOGW("Option --render-expired-frames has been removed. This "
|
||||||
|
"flag has been ignored.");
|
||||||
break;
|
break;
|
||||||
case OPT_WINDOW_TITLE:
|
case OPT_WINDOW_TITLE:
|
||||||
opts->window_title = optarg;
|
opts->window_title = optarg;
|
||||||
@@ -886,16 +914,39 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
case OPT_LEGACY_PASTE:
|
case OPT_LEGACY_PASTE:
|
||||||
opts->legacy_paste = true;
|
opts->legacy_paste = true;
|
||||||
break;
|
break;
|
||||||
|
case OPT_POWER_OFF_ON_CLOSE:
|
||||||
|
opts->power_off_on_close = true;
|
||||||
|
break;
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
case OPT_V4L2_SINK:
|
||||||
|
opts->v4l2_device = optarg;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
if (!opts->display && !opts->record_filename && !opts->v4l2_device) {
|
||||||
|
LOGE("-N/--no-display requires either screen recording (-r/--record)"
|
||||||
|
" or sink to v4l2loopback device (--v4l2-sink)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts->v4l2_device && opts->lock_video_orientation
|
||||||
|
== SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
|
||||||
|
LOGI("Video orientation is locked for v4l2 sink. "
|
||||||
|
"See --lock-video-orientation.");
|
||||||
|
opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
|
||||||
|
}
|
||||||
|
#else
|
||||||
if (!opts->display && !opts->record_filename) {
|
if (!opts->display && !opts->record_filename) {
|
||||||
LOGE("-N/--no-display requires screen recording (-r/--record)");
|
LOGE("-N/--no-display requires screen recording (-r/--record)");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
int index = optind;
|
int index = optind;
|
||||||
if (index < argc) {
|
if (index < argc) {
|
||||||
@@ -911,7 +962,8 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
if (opts->record_filename && !opts->record_format) {
|
if (opts->record_filename && !opts->record_format) {
|
||||||
opts->record_format = guess_record_format(opts->record_filename);
|
opts->record_format = guess_record_format(opts->record_filename);
|
||||||
if (!opts->record_format) {
|
if (!opts->record_format) {
|
||||||
LOGE("No format specified for \"%s\" (try with -F mkv)",
|
LOGE("No format specified for \"%s\" "
|
||||||
|
"(try with --record-format=mkv)",
|
||||||
opts->record_filename);
|
opts->record_filename);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
#ifndef SCRCPY_CLI_H
|
#ifndef SCRCPY_CLI_H
|
||||||
#define SCRCPY_CLI_H
|
#define SCRCPY_CLI_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "scrcpy.h"
|
#include "scrcpy.h"
|
||||||
|
|
||||||
struct scrcpy_cli_args {
|
struct scrcpy_cli_args {
|
||||||
|
|||||||
@@ -1,35 +1,14 @@
|
|||||||
#ifndef COMMON_H
|
#ifndef COMMON_H
|
||||||
#define COMMON_H
|
#define COMMON_H
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "compat.h"
|
||||||
|
|
||||||
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
|
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
|
||||||
#define MIN(X,Y) (X) < (Y) ? (X) : (Y)
|
#define MIN(X,Y) (X) < (Y) ? (X) : (Y)
|
||||||
#define MAX(X,Y) (X) > (Y) ? (X) : (Y)
|
#define MAX(X,Y) (X) > (Y) ? (X) : (Y)
|
||||||
|
|
||||||
struct size {
|
#define container_of(ptr, type, member) \
|
||||||
uint16_t width;
|
((type *) (((char *) (ptr)) - offsetof(type, member)))
|
||||||
uint16_t height;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct point {
|
|
||||||
int32_t x;
|
|
||||||
int32_t y;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct position {
|
|
||||||
// The video screen size may be different from the real device screen size,
|
|
||||||
// so store to which size the absolute position apply, to scale it
|
|
||||||
// accordingly.
|
|
||||||
struct size screen_size;
|
|
||||||
struct point point;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct port_range {
|
|
||||||
uint16_t first;
|
|
||||||
uint16_t last;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
14
app/src/compat.c
Normal file
14
app/src/compat.c
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#include "compat.h"
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#ifndef HAVE_STRDUP
|
||||||
|
char *strdup(const char *s) {
|
||||||
|
size_t size = strlen(s) + 1;
|
||||||
|
char *dup = malloc(size);
|
||||||
|
if (dup) {
|
||||||
|
memcpy(dup, s, size);
|
||||||
|
}
|
||||||
|
return dup;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
#ifndef COMPAT_H
|
#ifndef COMPAT_H
|
||||||
#define COMPAT_H
|
#define COMPAT_H
|
||||||
|
|
||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#define _XOPEN_SOURCE 700
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#ifdef __APPLE__
|
||||||
|
# define _DARWIN_C_SOURCE
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <libavformat/version.h>
|
#include <libavformat/version.h>
|
||||||
#include <SDL2/SDL_version.h>
|
#include <SDL2/SDL_version.h>
|
||||||
|
|
||||||
// In ffmpeg/doc/APIchanges:
|
|
||||||
// 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h
|
|
||||||
// Add AVStream.codecpar, deprecate AVStream.codec.
|
|
||||||
#if (LIBAVFORMAT_VERSION_MICRO >= 100 /* FFmpeg */ && \
|
|
||||||
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)) \
|
|
||||||
|| (LIBAVFORMAT_VERSION_MICRO < 100 && /* Libav */ \
|
|
||||||
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 5, 0))
|
|
||||||
# define SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// In ffmpeg/doc/APIchanges:
|
// In ffmpeg/doc/APIchanges:
|
||||||
// 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
|
// 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
|
||||||
// Deprecate use of av_register_input_format(), av_register_output_format(),
|
// Deprecate use of av_register_input_format(), av_register_output_format(),
|
||||||
@@ -25,13 +22,16 @@
|
|||||||
# define SCRCPY_LAVF_REQUIRES_REGISTER_ALL
|
# define SCRCPY_LAVF_REQUIRES_REGISTER_ALL
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// In ffmpeg/doc/APIchanges:
|
// In ffmpeg/doc/APIchanges:
|
||||||
// 2016-04-21 - 7fc329e - lavc 57.37.100 - avcodec.h
|
// 2018-01-28 - ea3672b7d6 - lavf 58.7.100 - avformat.h
|
||||||
// Add a new audio/video encoding and decoding API with decoupled input
|
// Deprecate AVFormatContext filename field which had limited length, use the
|
||||||
// and output -- avcodec_send_packet(), avcodec_receive_frame(),
|
// new dynamically allocated url field instead.
|
||||||
// avcodec_send_frame() and avcodec_receive_packet().
|
//
|
||||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100)
|
// 2018-01-28 - ea3672b7d6 - lavf 58.7.100 - avformat.h
|
||||||
# define SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API
|
// Add url field to AVFormatContext and add ff_format_set_url helper function.
|
||||||
|
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 7, 100)
|
||||||
|
# define SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 5)
|
#if SDL_VERSION_ATLEAST(2, 0, 5)
|
||||||
@@ -48,4 +48,8 @@
|
|||||||
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
|
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef HAVE_STRDUP
|
||||||
|
char *strdup(const char *s);
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,13 +1,60 @@
|
|||||||
#include "control_msg.h"
|
#include "control_msg.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "util/buffer_util.h"
|
#include "util/buffer_util.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str_util.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map an enum value to a string based on an array, without crashing on an
|
||||||
|
* out-of-bounds index.
|
||||||
|
*/
|
||||||
|
#define ENUM_TO_LABEL(labels, value) \
|
||||||
|
((size_t) (value) < ARRAY_LEN(labels) ? labels[value] : "???")
|
||||||
|
|
||||||
|
#define KEYEVENT_ACTION_LABEL(value) \
|
||||||
|
ENUM_TO_LABEL(android_keyevent_action_labels, value)
|
||||||
|
|
||||||
|
#define MOTIONEVENT_ACTION_LABEL(value) \
|
||||||
|
ENUM_TO_LABEL(android_motionevent_action_labels, value)
|
||||||
|
|
||||||
|
#define SCREEN_POWER_MODE_LABEL(value) \
|
||||||
|
ENUM_TO_LABEL(screen_power_mode_labels, value)
|
||||||
|
|
||||||
|
static const char *const android_keyevent_action_labels[] = {
|
||||||
|
"down",
|
||||||
|
"up",
|
||||||
|
"multi",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *const android_motionevent_action_labels[] = {
|
||||||
|
"down",
|
||||||
|
"up",
|
||||||
|
"move",
|
||||||
|
"cancel",
|
||||||
|
"outside",
|
||||||
|
"ponter-down",
|
||||||
|
"pointer-up",
|
||||||
|
"hover-move",
|
||||||
|
"scroll",
|
||||||
|
"hover-enter"
|
||||||
|
"hover-exit",
|
||||||
|
"btn-press",
|
||||||
|
"btn-release",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *const screen_power_mode_labels[] = {
|
||||||
|
"off",
|
||||||
|
"doze",
|
||||||
|
"normal",
|
||||||
|
"doze-suspend",
|
||||||
|
"suspend",
|
||||||
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
write_position(uint8_t *buf, const struct 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);
|
||||||
@@ -67,6 +114,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
|||||||
buffer_write32be(&buf[17],
|
buffer_write32be(&buf[17],
|
||||||
(uint32_t) msg->inject_scroll_event.vscroll);
|
(uint32_t) msg->inject_scroll_event.vscroll);
|
||||||
return 21;
|
return 21;
|
||||||
|
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
||||||
|
buf[1] = msg->inject_keycode.action;
|
||||||
|
return 2;
|
||||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
|
||||||
buf[1] = !!msg->set_clipboard.paste;
|
buf[1] = !!msg->set_clipboard.paste;
|
||||||
size_t len = write_string(msg->set_clipboard.text,
|
size_t len = write_string(msg->set_clipboard.text,
|
||||||
@@ -77,9 +127,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
|||||||
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||||
buf[1] = msg->set_screen_power_mode.mode;
|
buf[1] = msg->set_screen_power_mode.mode;
|
||||||
return 2;
|
return 2;
|
||||||
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
|
||||||
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||||
|
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||||
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||||
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||||
// no additional data
|
// no additional data
|
||||||
@@ -90,14 +140,102 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
control_msg_log(const struct control_msg *msg) {
|
||||||
|
#define LOG_CMSG(fmt, ...) LOGV("input: " fmt, ## __VA_ARGS__)
|
||||||
|
switch (msg->type) {
|
||||||
|
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
|
||||||
|
LOG_CMSG("key %-4s code=%d repeat=%" PRIu32 " meta=%06lx",
|
||||||
|
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action),
|
||||||
|
(int) msg->inject_keycode.keycode,
|
||||||
|
msg->inject_keycode.repeat,
|
||||||
|
(long) msg->inject_keycode.metastate);
|
||||||
|
break;
|
||||||
|
case CONTROL_MSG_TYPE_INJECT_TEXT:
|
||||||
|
LOG_CMSG("text \"%s\"", msg->inject_text.text);
|
||||||
|
break;
|
||||||
|
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: {
|
||||||
|
int action = msg->inject_touch_event.action
|
||||||
|
& AMOTION_EVENT_ACTION_MASK;
|
||||||
|
uint64_t id = msg->inject_touch_event.pointer_id;
|
||||||
|
if (id == POINTER_ID_MOUSE || id == POINTER_ID_VIRTUAL_FINGER) {
|
||||||
|
// string pointer id
|
||||||
|
LOG_CMSG("touch [id=%s] %-4s position=%" PRIi32 ",%" PRIi32
|
||||||
|
" pressure=%g buttons=%06lx",
|
||||||
|
id == POINTER_ID_MOUSE ? "mouse" : "vfinger",
|
||||||
|
MOTIONEVENT_ACTION_LABEL(action),
|
||||||
|
msg->inject_touch_event.position.point.x,
|
||||||
|
msg->inject_touch_event.position.point.y,
|
||||||
|
msg->inject_touch_event.pressure,
|
||||||
|
(long) msg->inject_touch_event.buttons);
|
||||||
|
} else {
|
||||||
|
// numeric pointer id
|
||||||
|
#ifndef __WIN32
|
||||||
|
# define PRIu64_ PRIu64
|
||||||
|
#else
|
||||||
|
# define PRIu64_ "I64u" // Windows...
|
||||||
|
#endif
|
||||||
|
LOG_CMSG("touch [id=%" PRIu64_ "] %-4s position=%" PRIi32 ",%"
|
||||||
|
PRIi32 " pressure=%g buttons=%06lx",
|
||||||
|
id,
|
||||||
|
MOTIONEVENT_ACTION_LABEL(action),
|
||||||
|
msg->inject_touch_event.position.point.x,
|
||||||
|
msg->inject_touch_event.position.point.y,
|
||||||
|
msg->inject_touch_event.pressure,
|
||||||
|
(long) msg->inject_touch_event.buttons);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
|
||||||
|
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32
|
||||||
|
" vscroll=%" PRIi32,
|
||||||
|
msg->inject_scroll_event.position.point.x,
|
||||||
|
msg->inject_scroll_event.position.point.y,
|
||||||
|
msg->inject_scroll_event.hscroll,
|
||||||
|
msg->inject_scroll_event.vscroll);
|
||||||
|
break;
|
||||||
|
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
|
||||||
|
LOG_CMSG("back-or-screen-on %s",
|
||||||
|
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action));
|
||||||
|
break;
|
||||||
|
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
||||||
|
LOG_CMSG("clipboard %s \"%s\"",
|
||||||
|
msg->set_clipboard.paste ? "paste" : "copy",
|
||||||
|
msg->set_clipboard.text);
|
||||||
|
break;
|
||||||
|
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||||
|
LOG_CMSG("power mode %s",
|
||||||
|
SCREEN_POWER_MODE_LABEL(msg->set_screen_power_mode.mode));
|
||||||
|
break;
|
||||||
|
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
|
LOG_CMSG("expand notification panel");
|
||||||
|
break;
|
||||||
|
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||||
|
LOG_CMSG("expand settings panel");
|
||||||
|
break;
|
||||||
|
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||||
|
LOG_CMSG("collapse panels");
|
||||||
|
break;
|
||||||
|
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
|
||||||
|
LOG_CMSG("get clipboard");
|
||||||
|
break;
|
||||||
|
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||||
|
LOG_CMSG("rotate device");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
control_msg_destroy(struct control_msg *msg) {
|
control_msg_destroy(struct control_msg *msg) {
|
||||||
switch (msg->type) {
|
switch (msg->type) {
|
||||||
case CONTROL_MSG_TYPE_INJECT_TEXT:
|
case CONTROL_MSG_TYPE_INJECT_TEXT:
|
||||||
SDL_free(msg->inject_text.text);
|
free(msg->inject_text.text);
|
||||||
break;
|
break;
|
||||||
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
|
||||||
SDL_free(msg->set_clipboard.text);
|
free(msg->set_clipboard.text);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// do nothing
|
// do nothing
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
#ifndef CONTROLMSG_H
|
#ifndef CONTROLMSG_H
|
||||||
#define CONTROLMSG_H
|
#define CONTROLMSG_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "android/input.h"
|
#include "android/input.h"
|
||||||
#include "android/keycodes.h"
|
#include "android/keycodes.h"
|
||||||
#include "common.h"
|
#include "coords.h"
|
||||||
|
|
||||||
#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
|
#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
|
||||||
|
|
||||||
@@ -16,8 +17,8 @@
|
|||||||
// type: 1 byte; paste flag: 1 byte; length: 4 bytes
|
// type: 1 byte; paste flag: 1 byte; length: 4 bytes
|
||||||
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6)
|
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 6)
|
||||||
|
|
||||||
#define POINTER_ID_MOUSE UINT64_C(-1);
|
#define POINTER_ID_MOUSE UINT64_C(-1)
|
||||||
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2);
|
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2)
|
||||||
|
|
||||||
enum control_msg_type {
|
enum control_msg_type {
|
||||||
CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
CONTROL_MSG_TYPE_INJECT_KEYCODE,
|
||||||
@@ -26,7 +27,8 @@ enum control_msg_type {
|
|||||||
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
|
||||||
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
||||||
CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
|
CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
|
||||||
CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
|
CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
|
||||||
|
CONTROL_MSG_TYPE_COLLAPSE_PANELS,
|
||||||
CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
CONTROL_MSG_TYPE_GET_CLIPBOARD,
|
||||||
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||||
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
||||||
@@ -49,7 +51,7 @@ struct control_msg {
|
|||||||
enum android_metastate metastate;
|
enum android_metastate metastate;
|
||||||
} inject_keycode;
|
} inject_keycode;
|
||||||
struct {
|
struct {
|
||||||
char *text; // owned, to be freed by SDL_free()
|
char *text; // owned, to be freed by free()
|
||||||
} inject_text;
|
} inject_text;
|
||||||
struct {
|
struct {
|
||||||
enum android_motionevent_action action;
|
enum android_motionevent_action action;
|
||||||
@@ -64,7 +66,11 @@ struct control_msg {
|
|||||||
int32_t vscroll;
|
int32_t vscroll;
|
||||||
} inject_scroll_event;
|
} inject_scroll_event;
|
||||||
struct {
|
struct {
|
||||||
char *text; // owned, to be freed by SDL_free()
|
enum android_keyevent_action action; // action for the BACK key
|
||||||
|
// screen may only be turned on on ACTION_DOWN
|
||||||
|
} back_or_screen_on;
|
||||||
|
struct {
|
||||||
|
char *text; // owned, to be freed by free()
|
||||||
bool paste;
|
bool paste;
|
||||||
} set_clipboard;
|
} set_clipboard;
|
||||||
struct {
|
struct {
|
||||||
@@ -78,6 +84,9 @@ struct control_msg {
|
|||||||
size_t
|
size_t
|
||||||
control_msg_serialize(const struct control_msg *msg, unsigned char *buf);
|
control_msg_serialize(const struct control_msg *msg, unsigned char *buf);
|
||||||
|
|
||||||
|
void
|
||||||
|
control_msg_log(const struct control_msg *msg);
|
||||||
|
|
||||||
void
|
void
|
||||||
control_msg_destroy(struct control_msg *msg);
|
control_msg_destroy(struct control_msg *msg);
|
||||||
|
|
||||||
|
|||||||
@@ -2,26 +2,27 @@
|
|||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
controller_init(struct controller *controller, socket_t control_socket) {
|
controller_init(struct controller *controller, socket_t control_socket) {
|
||||||
cbuf_init(&controller->queue);
|
cbuf_init(&controller->queue);
|
||||||
|
|
||||||
if (!receiver_init(&controller->receiver, control_socket)) {
|
bool ok = receiver_init(&controller->receiver, control_socket);
|
||||||
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(controller->mutex = SDL_CreateMutex())) {
|
ok = sc_mutex_init(&controller->mutex);
|
||||||
|
if (!ok) {
|
||||||
receiver_destroy(&controller->receiver);
|
receiver_destroy(&controller->receiver);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(controller->msg_cond = SDL_CreateCond())) {
|
ok = sc_cond_init(&controller->msg_cond);
|
||||||
|
if (!ok) {
|
||||||
receiver_destroy(&controller->receiver);
|
receiver_destroy(&controller->receiver);
|
||||||
SDL_DestroyMutex(controller->mutex);
|
sc_mutex_destroy(&controller->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,8 +34,8 @@ controller_init(struct controller *controller, socket_t control_socket) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
controller_destroy(struct controller *controller) {
|
controller_destroy(struct controller *controller) {
|
||||||
SDL_DestroyCond(controller->msg_cond);
|
sc_cond_destroy(&controller->msg_cond);
|
||||||
SDL_DestroyMutex(controller->mutex);
|
sc_mutex_destroy(&controller->mutex);
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
while (cbuf_take(&controller->queue, &msg)) {
|
while (cbuf_take(&controller->queue, &msg)) {
|
||||||
@@ -47,13 +48,17 @@ controller_destroy(struct controller *controller) {
|
|||||||
bool
|
bool
|
||||||
controller_push_msg(struct controller *controller,
|
controller_push_msg(struct controller *controller,
|
||||||
const struct control_msg *msg) {
|
const struct control_msg *msg) {
|
||||||
mutex_lock(controller->mutex);
|
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
||||||
|
control_msg_log(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_mutex_lock(&controller->mutex);
|
||||||
bool was_empty = cbuf_is_empty(&controller->queue);
|
bool was_empty = cbuf_is_empty(&controller->queue);
|
||||||
bool res = cbuf_push(&controller->queue, *msg);
|
bool res = cbuf_push(&controller->queue, *msg);
|
||||||
if (was_empty) {
|
if (was_empty) {
|
||||||
cond_signal(controller->msg_cond);
|
sc_cond_signal(&controller->msg_cond);
|
||||||
}
|
}
|
||||||
mutex_unlock(controller->mutex);
|
sc_mutex_unlock(&controller->mutex);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,12 +66,12 @@ static bool
|
|||||||
process_msg(struct controller *controller,
|
process_msg(struct controller *controller,
|
||||||
const struct control_msg *msg) {
|
const struct control_msg *msg) {
|
||||||
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
|
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
|
||||||
int length = control_msg_serialize(msg, serialized_msg);
|
size_t length = control_msg_serialize(msg, serialized_msg);
|
||||||
if (!length) {
|
if (!length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int w = net_send_all(controller->control_socket, serialized_msg, length);
|
int w = net_send_all(controller->control_socket, serialized_msg, length);
|
||||||
return w == length;
|
return (size_t) w == length;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -74,20 +79,20 @@ run_controller(void *data) {
|
|||||||
struct controller *controller = data;
|
struct controller *controller = data;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
mutex_lock(controller->mutex);
|
sc_mutex_lock(&controller->mutex);
|
||||||
while (!controller->stopped && cbuf_is_empty(&controller->queue)) {
|
while (!controller->stopped && cbuf_is_empty(&controller->queue)) {
|
||||||
cond_wait(controller->msg_cond, controller->mutex);
|
sc_cond_wait(&controller->msg_cond, &controller->mutex);
|
||||||
}
|
}
|
||||||
if (controller->stopped) {
|
if (controller->stopped) {
|
||||||
// stop immediately, do not process further msgs
|
// stop immediately, do not process further msgs
|
||||||
mutex_unlock(controller->mutex);
|
sc_mutex_unlock(&controller->mutex);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
bool non_empty = cbuf_take(&controller->queue, &msg);
|
bool non_empty = cbuf_take(&controller->queue, &msg);
|
||||||
assert(non_empty);
|
assert(non_empty);
|
||||||
(void) non_empty;
|
(void) non_empty;
|
||||||
mutex_unlock(controller->mutex);
|
sc_mutex_unlock(&controller->mutex);
|
||||||
|
|
||||||
bool ok = process_msg(controller, &msg);
|
bool ok = process_msg(controller, &msg);
|
||||||
control_msg_destroy(&msg);
|
control_msg_destroy(&msg);
|
||||||
@@ -103,16 +108,16 @@ bool
|
|||||||
controller_start(struct controller *controller) {
|
controller_start(struct controller *controller) {
|
||||||
LOGD("Starting controller thread");
|
LOGD("Starting controller thread");
|
||||||
|
|
||||||
controller->thread = SDL_CreateThread(run_controller, "controller",
|
bool ok = sc_thread_create(&controller->thread, run_controller,
|
||||||
controller);
|
"controller", controller);
|
||||||
if (!controller->thread) {
|
if (!ok) {
|
||||||
LOGC("Could not start controller thread");
|
LOGC("Could not start controller thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!receiver_start(&controller->receiver)) {
|
if (!receiver_start(&controller->receiver)) {
|
||||||
controller_stop(controller);
|
controller_stop(controller);
|
||||||
SDL_WaitThread(controller->thread, NULL);
|
sc_thread_join(&controller->thread, NULL);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,14 +126,14 @@ controller_start(struct controller *controller) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
controller_stop(struct controller *controller) {
|
controller_stop(struct controller *controller) {
|
||||||
mutex_lock(controller->mutex);
|
sc_mutex_lock(&controller->mutex);
|
||||||
controller->stopped = true;
|
controller->stopped = true;
|
||||||
cond_signal(controller->msg_cond);
|
sc_cond_signal(&controller->msg_cond);
|
||||||
mutex_unlock(controller->mutex);
|
sc_mutex_unlock(&controller->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
controller_join(struct controller *controller) {
|
controller_join(struct controller *controller) {
|
||||||
SDL_WaitThread(controller->thread, NULL);
|
sc_thread_join(&controller->thread, NULL);
|
||||||
receiver_join(&controller->receiver);
|
receiver_join(&controller->receiver);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
#ifndef CONTROLLER_H
|
#ifndef CONTROLLER_H
|
||||||
#define CONTROLLER_H
|
#define CONTROLLER_H
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include "common.h"
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
#include <SDL2/SDL_thread.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "control_msg.h"
|
#include "control_msg.h"
|
||||||
#include "receiver.h"
|
#include "receiver.h"
|
||||||
#include "util/cbuf.h"
|
#include "util/cbuf.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
|
||||||
struct control_msg_queue CBUF(struct control_msg, 64);
|
struct control_msg_queue CBUF(struct control_msg, 64);
|
||||||
|
|
||||||
struct controller {
|
struct controller {
|
||||||
socket_t control_socket;
|
socket_t control_socket;
|
||||||
SDL_Thread *thread;
|
sc_thread thread;
|
||||||
SDL_mutex *mutex;
|
sc_mutex mutex;
|
||||||
SDL_cond *msg_cond;
|
sc_cond msg_cond;
|
||||||
bool stopped;
|
bool stopped;
|
||||||
struct control_msg_queue queue;
|
struct control_msg_queue queue;
|
||||||
struct receiver receiver;
|
struct receiver receiver;
|
||||||
|
|||||||
24
app/src/coords.h
Normal file
24
app/src/coords.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef SC_COORDS
|
||||||
|
#define SC_COORDS
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
struct size {
|
||||||
|
uint16_t width;
|
||||||
|
uint16_t height;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct point {
|
||||||
|
int32_t x;
|
||||||
|
int32_t y;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct position {
|
||||||
|
// The video screen size may be different from the real device screen size,
|
||||||
|
// so store to which size the absolute position apply, to scale it
|
||||||
|
// accordingly.
|
||||||
|
struct size screen_size;
|
||||||
|
struct point point;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,42 +1,43 @@
|
|||||||
#include "decoder.h"
|
#include "decoder.h"
|
||||||
|
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
#include <libavutil/time.h>
|
|
||||||
#include <SDL2/SDL_events.h>
|
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
#include <SDL2/SDL_thread.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "compat.h"
|
|
||||||
#include "events.h"
|
#include "events.h"
|
||||||
#include "recorder.h"
|
|
||||||
#include "video_buffer.h"
|
#include "video_buffer.h"
|
||||||
#include "util/buffer_util.h"
|
#include "trait/frame_sink.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
// set the decoded frame as ready for rendering, and notify
|
/** Downcast packet_sink to decoder */
|
||||||
|
#define DOWNCAST(SINK) container_of(SINK, struct decoder, packet_sink)
|
||||||
|
|
||||||
static void
|
static void
|
||||||
push_frame(struct decoder *decoder) {
|
decoder_close_first_sinks(struct decoder *decoder, unsigned count) {
|
||||||
bool previous_frame_skipped;
|
while (count) {
|
||||||
video_buffer_offer_decoded_frame(decoder->video_buffer,
|
struct sc_frame_sink *sink = decoder->sinks[--count];
|
||||||
&previous_frame_skipped);
|
sink->ops->close(sink);
|
||||||
if (previous_frame_skipped) {
|
|
||||||
// the previous EVENT_NEW_FRAME will consume this frame
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
static SDL_Event new_frame_event = {
|
|
||||||
.type = EVENT_NEW_FRAME,
|
|
||||||
};
|
|
||||||
SDL_PushEvent(&new_frame_event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static inline void
|
||||||
decoder_init(struct decoder *decoder, struct video_buffer *vb) {
|
decoder_close_sinks(struct decoder *decoder) {
|
||||||
decoder->video_buffer = vb;
|
decoder_close_first_sinks(decoder, decoder->sink_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
static bool
|
||||||
|
decoder_open_sinks(struct decoder *decoder) {
|
||||||
|
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
||||||
|
struct sc_frame_sink *sink = decoder->sinks[i];
|
||||||
|
if (!sink->ops->open(sink)) {
|
||||||
|
LOGE("Could not open frame sink %d", i);
|
||||||
|
decoder_close_first_sinks(decoder, i);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
decoder_open(struct decoder *decoder, const AVCodec *codec) {
|
decoder_open(struct decoder *decoder, const AVCodec *codec) {
|
||||||
decoder->codec_ctx = avcodec_alloc_context3(codec);
|
decoder->codec_ctx = avcodec_alloc_context3(codec);
|
||||||
if (!decoder->codec_ctx) {
|
if (!decoder->codec_ctx) {
|
||||||
@@ -50,52 +51,110 @@ decoder_open(struct decoder *decoder, const AVCodec *codec) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decoder->frame = av_frame_alloc();
|
||||||
|
if (!decoder->frame) {
|
||||||
|
LOGE("Could not create decoder frame");
|
||||||
|
avcodec_close(decoder->codec_ctx);
|
||||||
|
avcodec_free_context(&decoder->codec_ctx);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!decoder_open_sinks(decoder)) {
|
||||||
|
LOGE("Could not open decoder sinks");
|
||||||
|
av_frame_free(&decoder->frame);
|
||||||
|
avcodec_close(decoder->codec_ctx);
|
||||||
|
avcodec_free_context(&decoder->codec_ctx);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
decoder_close(struct decoder *decoder) {
|
decoder_close(struct decoder *decoder) {
|
||||||
|
decoder_close_sinks(decoder);
|
||||||
|
av_frame_free(&decoder->frame);
|
||||||
avcodec_close(decoder->codec_ctx);
|
avcodec_close(decoder->codec_ctx);
|
||||||
avcodec_free_context(&decoder->codec_ctx);
|
avcodec_free_context(&decoder->codec_ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
static bool
|
||||||
|
push_frame_to_sinks(struct decoder *decoder, const AVFrame *frame) {
|
||||||
|
for (unsigned i = 0; i < decoder->sink_count; ++i) {
|
||||||
|
struct sc_frame_sink *sink = decoder->sinks[i];
|
||||||
|
if (!sink->ops->push(sink, frame)) {
|
||||||
|
LOGE("Could not send frame to sink %d", i);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
decoder_push(struct decoder *decoder, const AVPacket *packet) {
|
decoder_push(struct decoder *decoder, const AVPacket *packet) {
|
||||||
// the new decoding/encoding API has been introduced by:
|
bool is_config = packet->pts == AV_NOPTS_VALUE;
|
||||||
// <http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=7fc329e2dd6226dfecaa4a1d7adf353bf2773726>
|
if (is_config) {
|
||||||
#ifdef SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API
|
// nothing to do
|
||||||
int ret;
|
return true;
|
||||||
if ((ret = avcodec_send_packet(decoder->codec_ctx, packet)) < 0) {
|
}
|
||||||
|
|
||||||
|
int ret = avcodec_send_packet(decoder->codec_ctx, packet);
|
||||||
|
if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
||||||
LOGE("Could not send video packet: %d", ret);
|
LOGE("Could not send video packet: %d", ret);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ret = avcodec_receive_frame(decoder->codec_ctx,
|
ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame);
|
||||||
decoder->video_buffer->decoding_frame);
|
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
// a frame was received
|
// a frame was received
|
||||||
push_frame(decoder);
|
bool ok = push_frame_to_sinks(decoder, decoder->frame);
|
||||||
|
// A frame lost should not make the whole pipeline fail. The error, if
|
||||||
|
// any, is already logged.
|
||||||
|
(void) ok;
|
||||||
|
|
||||||
|
av_frame_unref(decoder->frame);
|
||||||
} else if (ret != AVERROR(EAGAIN)) {
|
} else if (ret != AVERROR(EAGAIN)) {
|
||||||
LOGE("Could not receive video frame: %d", ret);
|
LOGE("Could not receive video frame: %d", ret);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
int got_picture;
|
|
||||||
int len = avcodec_decode_video2(decoder->codec_ctx,
|
|
||||||
decoder->video_buffer->decoding_frame,
|
|
||||||
&got_picture,
|
|
||||||
packet);
|
|
||||||
if (len < 0) {
|
|
||||||
LOGE("Could not decode video packet: %d", len);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (got_picture) {
|
|
||||||
push_frame(decoder);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static bool
|
||||||
decoder_interrupt(struct decoder *decoder) {
|
decoder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
|
||||||
video_buffer_interrupt(decoder->video_buffer);
|
struct decoder *decoder = DOWNCAST(sink);
|
||||||
|
return decoder_open(decoder, codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
decoder_packet_sink_close(struct sc_packet_sink *sink) {
|
||||||
|
struct decoder *decoder = DOWNCAST(sink);
|
||||||
|
decoder_close(decoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
decoder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) {
|
||||||
|
struct decoder *decoder = DOWNCAST(sink);
|
||||||
|
return decoder_push(decoder, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
decoder_init(struct decoder *decoder) {
|
||||||
|
decoder->sink_count = 0;
|
||||||
|
|
||||||
|
static const struct sc_packet_sink_ops ops = {
|
||||||
|
.open = decoder_packet_sink_open,
|
||||||
|
.close = decoder_packet_sink_close,
|
||||||
|
.push = decoder_packet_sink_push,
|
||||||
|
};
|
||||||
|
|
||||||
|
decoder->packet_sink.ops = &ops;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink) {
|
||||||
|
assert(decoder->sink_count < DECODER_MAX_SINKS);
|
||||||
|
assert(sink);
|
||||||
|
assert(sink->ops);
|
||||||
|
decoder->sinks[decoder->sink_count++] = sink;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,29 @@
|
|||||||
#ifndef DECODER_H
|
#ifndef DECODER_H
|
||||||
#define DECODER_H
|
#define DECODER_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include "trait/packet_sink.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
#include "config.h"
|
#define DECODER_MAX_SINKS 2
|
||||||
|
|
||||||
struct video_buffer;
|
|
||||||
|
|
||||||
struct decoder {
|
struct decoder {
|
||||||
struct video_buffer *video_buffer;
|
struct sc_packet_sink packet_sink; // packet sink trait
|
||||||
|
|
||||||
|
struct sc_frame_sink *sinks[DECODER_MAX_SINKS];
|
||||||
|
unsigned sink_count;
|
||||||
|
|
||||||
AVCodecContext *codec_ctx;
|
AVCodecContext *codec_ctx;
|
||||||
|
AVFrame *frame;
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
decoder_init(struct decoder *decoder, struct video_buffer *vb);
|
decoder_init(struct decoder *decoder);
|
||||||
|
|
||||||
bool
|
|
||||||
decoder_open(struct decoder *decoder, const AVCodec *codec);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
decoder_close(struct decoder *decoder);
|
decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink);
|
||||||
|
|
||||||
bool
|
|
||||||
decoder_push(struct decoder *decoder, const AVPacket *packet);
|
|
||||||
|
|
||||||
void
|
|
||||||
decoder_interrupt(struct decoder *decoder);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
#include "device.h"
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "util/log.h"
|
|
||||||
|
|
||||||
bool
|
|
||||||
device_read_info(socket_t device_socket, char *device_name, struct size *size) {
|
|
||||||
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
|
|
||||||
int r = net_recv_all(device_socket, buf, sizeof(buf));
|
|
||||||
if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
|
|
||||||
LOGE("Could not retrieve device information");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#ifndef DEVICE_H
|
|
||||||
#define DEVICE_H
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "common.h"
|
|
||||||
#include "util/net.h"
|
|
||||||
|
|
||||||
#define DEVICE_NAME_FIELD_LENGTH 64
|
|
||||||
|
|
||||||
// name must be at least DEVICE_NAME_FIELD_LENGTH bytes
|
|
||||||
bool
|
|
||||||
device_read_info(socket_t device_socket, char *device_name, struct size *size);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#include "device_msg.h"
|
#include "device_msg.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "util/buffer_util.h"
|
#include "util/buffer_util.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
|
|||||||
if (clipboard_len > len - 5) {
|
if (clipboard_len > len - 5) {
|
||||||
return 0; // not available
|
return 0; // not available
|
||||||
}
|
}
|
||||||
char *text = SDL_malloc(clipboard_len + 1);
|
char *text = malloc(clipboard_len + 1);
|
||||||
if (!text) {
|
if (!text) {
|
||||||
LOGW("Could not allocate text for clipboard");
|
LOGW("Could not allocate text for clipboard");
|
||||||
return -1;
|
return -1;
|
||||||
@@ -43,6 +43,6 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
|
|||||||
void
|
void
|
||||||
device_msg_destroy(struct device_msg *msg) {
|
device_msg_destroy(struct device_msg *msg) {
|
||||||
if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) {
|
if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) {
|
||||||
SDL_free(msg->clipboard.text);
|
free(msg->clipboard.text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
#ifndef DEVICEMSG_H
|
#ifndef DEVICEMSG_H
|
||||||
#define DEVICEMSG_H
|
#define DEVICEMSG_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#define DEVICE_MSG_MAX_SIZE (1 << 18) // 256k
|
#define DEVICE_MSG_MAX_SIZE (1 << 18) // 256k
|
||||||
// type: 1 byte; length: 4 bytes
|
// type: 1 byte; length: 4 bytes
|
||||||
#define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5)
|
#define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5)
|
||||||
@@ -19,7 +19,7 @@ struct device_msg {
|
|||||||
enum device_msg_type type;
|
enum device_msg_type type;
|
||||||
union {
|
union {
|
||||||
struct {
|
struct {
|
||||||
char *text; // owned, to be freed by SDL_free()
|
char *text; // owned, to be freed by free()
|
||||||
} clipboard;
|
} clipboard;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#include "event_converter.h"
|
#include "event_converter.h"
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#define MAP(FROM, TO) case FROM: *to = TO; return true
|
#define MAP(FROM, TO) case FROM: *to = TO; return true
|
||||||
#define FAIL default: return false
|
#define FAIL default: return false
|
||||||
|
|
||||||
@@ -16,7 +14,7 @@ convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
|
|||||||
|
|
||||||
static enum android_metastate
|
static enum android_metastate
|
||||||
autocomplete_metastate(enum android_metastate metastate) {
|
autocomplete_metastate(enum android_metastate metastate) {
|
||||||
// fill dependant flags
|
// fill dependent flags
|
||||||
if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
|
if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
|
||||||
metastate |= AMETA_SHIFT_ON;
|
metastate |= AMETA_SHIFT_ON;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
#ifndef CONVERT_H
|
#ifndef CONVERT_H
|
||||||
#define CONVERT_H
|
#define CONVERT_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <SDL2/SDL_events.h>
|
#include <SDL2/SDL_events.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "control_msg.h"
|
#include "control_msg.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
#define EVENT_NEW_SESSION SDL_USEREVENT
|
#define EVENT_NEW_FRAME SDL_USEREVENT
|
||||||
#define EVENT_NEW_FRAME (SDL_USEREVENT + 1)
|
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
|
||||||
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 2)
|
|
||||||
|
|||||||
@@ -3,16 +3,14 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "adb.h"
|
||||||
#include "command.h"
|
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
#define DEFAULT_PUSH_TARGET "/sdcard/"
|
#define DEFAULT_PUSH_TARGET "/sdcard/Download/"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
file_handler_request_destroy(struct file_handler_request *req) {
|
file_handler_request_destroy(struct file_handler_request *req) {
|
||||||
SDL_free(req->file);
|
free(req->file);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@@ -21,21 +19,23 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
|
|||||||
|
|
||||||
cbuf_init(&file_handler->queue);
|
cbuf_init(&file_handler->queue);
|
||||||
|
|
||||||
if (!(file_handler->mutex = SDL_CreateMutex())) {
|
bool ok = sc_mutex_init(&file_handler->mutex);
|
||||||
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(file_handler->event_cond = SDL_CreateCond())) {
|
ok = sc_cond_init(&file_handler->event_cond);
|
||||||
SDL_DestroyMutex(file_handler->mutex);
|
if (!ok) {
|
||||||
|
sc_mutex_destroy(&file_handler->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serial) {
|
if (serial) {
|
||||||
file_handler->serial = SDL_strdup(serial);
|
file_handler->serial = strdup(serial);
|
||||||
if (!file_handler->serial) {
|
if (!file_handler->serial) {
|
||||||
LOGW("Could not strdup serial");
|
LOGW("Could not strdup serial");
|
||||||
SDL_DestroyCond(file_handler->event_cond);
|
sc_cond_destroy(&file_handler->event_cond);
|
||||||
SDL_DestroyMutex(file_handler->mutex);
|
sc_mutex_destroy(&file_handler->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -55,9 +55,9 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
|
|||||||
|
|
||||||
void
|
void
|
||||||
file_handler_destroy(struct file_handler *file_handler) {
|
file_handler_destroy(struct file_handler *file_handler) {
|
||||||
SDL_DestroyCond(file_handler->event_cond);
|
sc_cond_destroy(&file_handler->event_cond);
|
||||||
SDL_DestroyMutex(file_handler->mutex);
|
sc_mutex_destroy(&file_handler->mutex);
|
||||||
SDL_free(file_handler->serial);
|
free(file_handler->serial);
|
||||||
|
|
||||||
struct file_handler_request req;
|
struct file_handler_request req;
|
||||||
while (cbuf_take(&file_handler->queue, &req)) {
|
while (cbuf_take(&file_handler->queue, &req)) {
|
||||||
@@ -93,13 +93,13 @@ file_handler_request(struct file_handler *file_handler,
|
|||||||
.file = file,
|
.file = file,
|
||||||
};
|
};
|
||||||
|
|
||||||
mutex_lock(file_handler->mutex);
|
sc_mutex_lock(&file_handler->mutex);
|
||||||
bool was_empty = cbuf_is_empty(&file_handler->queue);
|
bool was_empty = cbuf_is_empty(&file_handler->queue);
|
||||||
bool res = cbuf_push(&file_handler->queue, req);
|
bool res = cbuf_push(&file_handler->queue, req);
|
||||||
if (was_empty) {
|
if (was_empty) {
|
||||||
cond_signal(file_handler->event_cond);
|
sc_cond_signal(&file_handler->event_cond);
|
||||||
}
|
}
|
||||||
mutex_unlock(file_handler->mutex);
|
sc_mutex_unlock(&file_handler->mutex);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,14 +108,14 @@ run_file_handler(void *data) {
|
|||||||
struct file_handler *file_handler = data;
|
struct file_handler *file_handler = data;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
mutex_lock(file_handler->mutex);
|
sc_mutex_lock(&file_handler->mutex);
|
||||||
file_handler->current_process = 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)) {
|
||||||
cond_wait(file_handler->event_cond, file_handler->mutex);
|
sc_cond_wait(&file_handler->event_cond, &file_handler->mutex);
|
||||||
}
|
}
|
||||||
if (file_handler->stopped) {
|
if (file_handler->stopped) {
|
||||||
// stop immediately, do not process further events
|
// stop immediately, do not process further events
|
||||||
mutex_unlock(file_handler->mutex);
|
sc_mutex_unlock(&file_handler->mutex);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
struct file_handler_request req;
|
struct file_handler_request req;
|
||||||
@@ -133,16 +133,16 @@ run_file_handler(void *data) {
|
|||||||
file_handler->push_target);
|
file_handler->push_target);
|
||||||
}
|
}
|
||||||
file_handler->current_process = process;
|
file_handler->current_process = process;
|
||||||
mutex_unlock(file_handler->mutex);
|
sc_mutex_unlock(&file_handler->mutex);
|
||||||
|
|
||||||
if (req.action == ACTION_INSTALL_APK) {
|
if (req.action == ACTION_INSTALL_APK) {
|
||||||
if (process_check_success(process, "adb install")) {
|
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 (process_check_success(process, "adb push")) {
|
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 {
|
||||||
@@ -151,6 +151,14 @@ run_file_handler(void *data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sc_mutex_lock(&file_handler->mutex);
|
||||||
|
// Close the process (it is necessary already terminated)
|
||||||
|
// Execute this call with mutex locked to avoid race conditions with
|
||||||
|
// file_handler_stop()
|
||||||
|
process_close(file_handler->current_process);
|
||||||
|
file_handler->current_process = PROCESS_NONE;
|
||||||
|
sc_mutex_unlock(&file_handler->mutex);
|
||||||
|
|
||||||
file_handler_request_destroy(&req);
|
file_handler_request_destroy(&req);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@@ -160,9 +168,9 @@ bool
|
|||||||
file_handler_start(struct file_handler *file_handler) {
|
file_handler_start(struct file_handler *file_handler) {
|
||||||
LOGD("Starting file_handler thread");
|
LOGD("Starting file_handler thread");
|
||||||
|
|
||||||
file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler",
|
bool ok = sc_thread_create(&file_handler->thread, run_file_handler,
|
||||||
file_handler);
|
"file_handler", file_handler);
|
||||||
if (!file_handler->thread) {
|
if (!ok) {
|
||||||
LOGC("Could not start file_handler thread");
|
LOGC("Could not start file_handler thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -172,20 +180,18 @@ file_handler_start(struct file_handler *file_handler) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
file_handler_stop(struct file_handler *file_handler) {
|
file_handler_stop(struct file_handler *file_handler) {
|
||||||
mutex_lock(file_handler->mutex);
|
sc_mutex_lock(&file_handler->mutex);
|
||||||
file_handler->stopped = true;
|
file_handler->stopped = true;
|
||||||
cond_signal(file_handler->event_cond);
|
sc_cond_signal(&file_handler->event_cond);
|
||||||
if (file_handler->current_process != PROCESS_NONE) {
|
if (file_handler->current_process != PROCESS_NONE) {
|
||||||
if (!cmd_terminate(file_handler->current_process)) {
|
if (!process_terminate(file_handler->current_process)) {
|
||||||
LOGW("Could not terminate install process");
|
LOGW("Could not terminate push/install process");
|
||||||
}
|
}
|
||||||
cmd_simple_wait(file_handler->current_process, NULL);
|
|
||||||
file_handler->current_process = PROCESS_NONE;
|
|
||||||
}
|
}
|
||||||
mutex_unlock(file_handler->mutex);
|
sc_mutex_unlock(&file_handler->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
file_handler_join(struct file_handler *file_handler) {
|
file_handler_join(struct file_handler *file_handler) {
|
||||||
SDL_WaitThread(file_handler->thread, NULL);
|
sc_thread_join(&file_handler->thread, NULL);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
#ifndef FILE_HANDLER_H
|
#ifndef FILE_HANDLER_H
|
||||||
#define FILE_HANDLER_H
|
#define FILE_HANDLER_H
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include "common.h"
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
#include <SDL2/SDL_thread.h>
|
|
||||||
|
|
||||||
#include "config.h"
|
#include <stdbool.h>
|
||||||
#include "command.h"
|
|
||||||
|
#include "adb.h"
|
||||||
#include "util/cbuf.h"
|
#include "util/cbuf.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
ACTION_INSTALL_APK,
|
ACTION_INSTALL_APK,
|
||||||
@@ -24,9 +24,9 @@ struct file_handler_request_queue CBUF(struct file_handler_request, 16);
|
|||||||
struct file_handler {
|
struct file_handler {
|
||||||
char *serial;
|
char *serial;
|
||||||
const char *push_target;
|
const char *push_target;
|
||||||
SDL_Thread *thread;
|
sc_thread thread;
|
||||||
SDL_mutex *mutex;
|
sc_mutex mutex;
|
||||||
SDL_cond *event_cond;
|
sc_cond event_cond;
|
||||||
bool stopped;
|
bool stopped;
|
||||||
bool initialized;
|
bool initialized;
|
||||||
process_t current_process;
|
process_t current_process;
|
||||||
@@ -49,7 +49,7 @@ file_handler_stop(struct file_handler *file_handler);
|
|||||||
void
|
void
|
||||||
file_handler_join(struct file_handler *file_handler);
|
file_handler_join(struct file_handler *file_handler);
|
||||||
|
|
||||||
// take ownership of file, and will SDL_free() it
|
// take ownership of file, and will free() it
|
||||||
bool
|
bool
|
||||||
file_handler_request(struct file_handler *file_handler,
|
file_handler_request(struct file_handler *file_handler,
|
||||||
file_handler_action_t action,
|
file_handler_action_t action,
|
||||||
|
|||||||
@@ -3,26 +3,24 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <SDL2/SDL_timer.h>
|
#include <SDL2/SDL_timer.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
#define FPS_COUNTER_INTERVAL_MS 1000
|
#define FPS_COUNTER_INTERVAL_MS 1000
|
||||||
|
|
||||||
bool
|
bool
|
||||||
fps_counter_init(struct fps_counter *counter) {
|
fps_counter_init(struct fps_counter *counter) {
|
||||||
counter->mutex = SDL_CreateMutex();
|
bool ok = sc_mutex_init(&counter->mutex);
|
||||||
if (!counter->mutex) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
counter->state_cond = SDL_CreateCond();
|
ok = sc_cond_init(&counter->state_cond);
|
||||||
if (!counter->state_cond) {
|
if (!ok) {
|
||||||
SDL_DestroyMutex(counter->mutex);
|
sc_mutex_destroy(&counter->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
counter->thread = NULL;
|
counter->thread_started = false;
|
||||||
atomic_init(&counter->started, 0);
|
atomic_init(&counter->started, 0);
|
||||||
// no need to initialize the other fields, they are unused until started
|
// no need to initialize the other fields, they are unused until started
|
||||||
|
|
||||||
@@ -31,8 +29,8 @@ fps_counter_init(struct fps_counter *counter) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
fps_counter_destroy(struct fps_counter *counter) {
|
fps_counter_destroy(struct fps_counter *counter) {
|
||||||
SDL_DestroyCond(counter->state_cond);
|
sc_cond_destroy(&counter->state_cond);
|
||||||
SDL_DestroyMutex(counter->mutex);
|
sc_mutex_destroy(&counter->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
@@ -78,10 +76,10 @@ static int
|
|||||||
run_fps_counter(void *data) {
|
run_fps_counter(void *data) {
|
||||||
struct fps_counter *counter = data;
|
struct fps_counter *counter = data;
|
||||||
|
|
||||||
mutex_lock(counter->mutex);
|
sc_mutex_lock(&counter->mutex);
|
||||||
while (!counter->interrupted) {
|
while (!counter->interrupted) {
|
||||||
while (!counter->interrupted && !is_started(counter)) {
|
while (!counter->interrupted && !is_started(counter)) {
|
||||||
cond_wait(counter->state_cond, counter->mutex);
|
sc_cond_wait(&counter->state_cond, &counter->mutex);
|
||||||
}
|
}
|
||||||
while (!counter->interrupted && is_started(counter)) {
|
while (!counter->interrupted && is_started(counter)) {
|
||||||
uint32_t now = SDL_GetTicks();
|
uint32_t now = SDL_GetTicks();
|
||||||
@@ -91,32 +89,35 @@ run_fps_counter(void *data) {
|
|||||||
uint32_t remaining = counter->next_timestamp - now;
|
uint32_t remaining = counter->next_timestamp - now;
|
||||||
|
|
||||||
// ignore the reason (timeout or signaled), we just loop anyway
|
// ignore the reason (timeout or signaled), we just loop anyway
|
||||||
cond_wait_timeout(counter->state_cond, counter->mutex, remaining);
|
sc_cond_timedwait(&counter->state_cond, &counter->mutex, remaining);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mutex_unlock(counter->mutex);
|
sc_mutex_unlock(&counter->mutex);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
fps_counter_start(struct fps_counter *counter) {
|
fps_counter_start(struct fps_counter *counter) {
|
||||||
mutex_lock(counter->mutex);
|
sc_mutex_lock(&counter->mutex);
|
||||||
counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS;
|
counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS;
|
||||||
counter->nr_rendered = 0;
|
counter->nr_rendered = 0;
|
||||||
counter->nr_skipped = 0;
|
counter->nr_skipped = 0;
|
||||||
mutex_unlock(counter->mutex);
|
sc_mutex_unlock(&counter->mutex);
|
||||||
|
|
||||||
set_started(counter, true);
|
set_started(counter, true);
|
||||||
cond_signal(counter->state_cond);
|
sc_cond_signal(&counter->state_cond);
|
||||||
|
|
||||||
// counter->thread is always accessed from the same thread, no need to lock
|
// counter->thread_started and counter->thread are always accessed from the
|
||||||
if (!counter->thread) {
|
// same thread, no need to lock
|
||||||
counter->thread =
|
if (!counter->thread_started) {
|
||||||
SDL_CreateThread(run_fps_counter, "fps counter", counter);
|
bool ok = sc_thread_create(&counter->thread, run_fps_counter,
|
||||||
if (!counter->thread) {
|
"fps counter", counter);
|
||||||
|
if (!ok) {
|
||||||
LOGE("Could not start FPS counter thread");
|
LOGE("Could not start FPS counter thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
counter->thread_started = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -125,7 +126,7 @@ fps_counter_start(struct fps_counter *counter) {
|
|||||||
void
|
void
|
||||||
fps_counter_stop(struct fps_counter *counter) {
|
fps_counter_stop(struct fps_counter *counter) {
|
||||||
set_started(counter, false);
|
set_started(counter, false);
|
||||||
cond_signal(counter->state_cond);
|
sc_cond_signal(&counter->state_cond);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@@ -135,21 +136,25 @@ fps_counter_is_started(struct fps_counter *counter) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
fps_counter_interrupt(struct fps_counter *counter) {
|
fps_counter_interrupt(struct fps_counter *counter) {
|
||||||
if (!counter->thread) {
|
if (!counter->thread_started) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_lock(counter->mutex);
|
sc_mutex_lock(&counter->mutex);
|
||||||
counter->interrupted = true;
|
counter->interrupted = true;
|
||||||
mutex_unlock(counter->mutex);
|
sc_mutex_unlock(&counter->mutex);
|
||||||
// wake up blocking wait
|
// wake up blocking wait
|
||||||
cond_signal(counter->state_cond);
|
sc_cond_signal(&counter->state_cond);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
fps_counter_join(struct fps_counter *counter) {
|
fps_counter_join(struct fps_counter *counter) {
|
||||||
if (counter->thread) {
|
if (counter->thread_started) {
|
||||||
SDL_WaitThread(counter->thread, NULL);
|
// interrupted must be set by the thread calling join(), so no need to
|
||||||
|
// lock for the assertion
|
||||||
|
assert(counter->interrupted);
|
||||||
|
|
||||||
|
sc_thread_join(&counter->thread, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,11 +164,11 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_lock(counter->mutex);
|
sc_mutex_lock(&counter->mutex);
|
||||||
uint32_t now = SDL_GetTicks();
|
uint32_t now = SDL_GetTicks();
|
||||||
check_interval_expired(counter, now);
|
check_interval_expired(counter, now);
|
||||||
++counter->nr_rendered;
|
++counter->nr_rendered;
|
||||||
mutex_unlock(counter->mutex);
|
sc_mutex_unlock(&counter->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -172,9 +177,9 @@ fps_counter_add_skipped_frame(struct fps_counter *counter) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_lock(counter->mutex);
|
sc_mutex_lock(&counter->mutex);
|
||||||
uint32_t now = SDL_GetTicks();
|
uint32_t now = SDL_GetTicks();
|
||||||
check_interval_expired(counter, now);
|
check_interval_expired(counter, now);
|
||||||
++counter->nr_skipped;
|
++counter->nr_skipped;
|
||||||
mutex_unlock(counter->mutex);
|
sc_mutex_unlock(&counter->mutex);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
#ifndef FPSCOUNTER_H
|
#ifndef FPSCOUNTER_H
|
||||||
#define FPSCOUNTER_H
|
#define FPSCOUNTER_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
#include <SDL2/SDL_thread.h>
|
|
||||||
|
|
||||||
#include "config.h"
|
#include "util/thread.h"
|
||||||
|
|
||||||
struct fps_counter {
|
struct fps_counter {
|
||||||
SDL_Thread *thread;
|
sc_thread thread;
|
||||||
SDL_mutex *mutex;
|
sc_mutex mutex;
|
||||||
SDL_cond *state_cond;
|
sc_cond state_cond;
|
||||||
|
|
||||||
|
bool thread_started;
|
||||||
|
|
||||||
// atomic so that we can check without locking the mutex
|
// atomic so that we can check without locking the mutex
|
||||||
// if the FPS counter is disabled, we don't want to lock unnecessarily
|
// if the FPS counter is disabled, we don't want to lock unnecessarily
|
||||||
|
|||||||
@@ -3,9 +3,7 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <SDL2/SDL_keycode.h>
|
#include <SDL2/SDL_keycode.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "event_converter.h"
|
#include "event_converter.h"
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
static const int ACTION_DOWN = 1;
|
static const int ACTION_DOWN = 1;
|
||||||
@@ -54,9 +52,13 @@ is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
input_manager_init(struct input_manager *im,
|
input_manager_init(struct input_manager *im, struct controller *controller,
|
||||||
const struct scrcpy_options *options)
|
struct screen *screen,
|
||||||
{
|
const struct scrcpy_options *options) {
|
||||||
|
im->controller = controller;
|
||||||
|
im->screen = screen;
|
||||||
|
im->repeat = 0;
|
||||||
|
|
||||||
im->control = options->control;
|
im->control = options->control;
|
||||||
im->forward_key_repeat = options->forward_key_repeat;
|
im->forward_key_repeat = options->forward_key_repeat;
|
||||||
im->prefer_text = options->prefer_text;
|
im->prefer_text = options->prefer_text;
|
||||||
@@ -74,6 +76,10 @@ input_manager_init(struct input_manager *im,
|
|||||||
im->sdl_shortcut_mods.count = shortcut_mods->count;
|
im->sdl_shortcut_mods.count = shortcut_mods->count;
|
||||||
|
|
||||||
im->vfinger_down = false;
|
im->vfinger_down = false;
|
||||||
|
|
||||||
|
im->last_keycode = SDLK_UNKNOWN;
|
||||||
|
im->last_mod = 0;
|
||||||
|
im->key_repeat = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -148,13 +154,25 @@ action_cut(struct controller *controller, int actions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// turn the screen on if it was off, press BACK otherwise
|
// turn the screen on if it was off, press BACK otherwise
|
||||||
|
// If the screen is off, it is turned on only on ACTION_DOWN
|
||||||
static void
|
static void
|
||||||
press_back_or_turn_screen_on(struct controller *controller) {
|
press_back_or_turn_screen_on(struct controller *controller, int actions) {
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
|
msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
|
||||||
|
|
||||||
|
if (actions & ACTION_DOWN) {
|
||||||
|
msg.back_or_screen_on.action = AKEY_EVENT_ACTION_DOWN;
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
LOGW("Could not request 'press back or turn screen on'");
|
LOGW("Could not request 'press back or turn screen on'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actions & ACTION_UP) {
|
||||||
|
msg.back_or_screen_on.action = AKEY_EVENT_ACTION_UP;
|
||||||
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
|
LOGW("Could not request 'press back or turn screen on'");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,9 +187,19 @@ expand_notification_panel(struct controller *controller) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
collapse_notification_panel(struct controller *controller) {
|
expand_settings_panel(struct controller *controller) {
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL;
|
msg.type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL;
|
||||||
|
|
||||||
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
|
LOGW("Could not request 'expand settings panel'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
collapse_panels(struct controller *controller) {
|
||||||
|
struct control_msg msg;
|
||||||
|
msg.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS;
|
||||||
|
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
LOGW("Could not request 'collapse notification panel'");
|
LOGW("Could not request 'collapse notification panel'");
|
||||||
@@ -191,13 +219,20 @@ set_device_clipboard(struct controller *controller, bool paste) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *text_dup = strdup(text);
|
||||||
|
SDL_free(text);
|
||||||
|
if (!text_dup) {
|
||||||
|
LOGW("Could not strdup input text");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
|
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
|
||||||
msg.set_clipboard.text = text;
|
msg.set_clipboard.text = text_dup;
|
||||||
msg.set_clipboard.paste = paste;
|
msg.set_clipboard.paste = paste;
|
||||||
|
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
SDL_free(text);
|
free(text_dup);
|
||||||
LOGW("Could not request 'set device clipboard'");
|
LOGW("Could not request 'set device clipboard'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,11 +278,18 @@ clipboard_paste(struct controller *controller) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *text_dup = strdup(text);
|
||||||
|
SDL_free(text);
|
||||||
|
if (!text_dup) {
|
||||||
|
LOGW("Could not strdup input text");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
|
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
|
||||||
msg.inject_text.text = text;
|
msg.inject_text.text = text_dup;
|
||||||
if (!controller_push_msg(controller, &msg)) {
|
if (!controller_push_msg(controller, &msg)) {
|
||||||
SDL_free(text);
|
free(text_dup);
|
||||||
LOGW("Could not request 'paste clipboard'");
|
LOGW("Could not request 'paste clipboard'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -274,7 +316,7 @@ rotate_client_right(struct screen *screen) {
|
|||||||
screen_set_rotation(screen, new_rotation);
|
screen_set_rotation(screen, new_rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
input_manager_process_text_input(struct input_manager *im,
|
input_manager_process_text_input(struct input_manager *im,
|
||||||
const SDL_TextInputEvent *event) {
|
const SDL_TextInputEvent *event) {
|
||||||
if (is_shortcut_mod(im, SDL_GetModState())) {
|
if (is_shortcut_mod(im, SDL_GetModState())) {
|
||||||
@@ -292,13 +334,13 @@ input_manager_process_text_input(struct input_manager *im,
|
|||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
|
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
|
||||||
msg.inject_text.text = SDL_strdup(event->text);
|
msg.inject_text.text = strdup(event->text);
|
||||||
if (!msg.inject_text.text) {
|
if (!msg.inject_text.text) {
|
||||||
LOGW("Could not strdup input text");
|
LOGW("Could not strdup input text");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!controller_push_msg(im->controller, &msg)) {
|
if (!controller_push_msg(im->controller, &msg)) {
|
||||||
SDL_free(msg.inject_text.text);
|
free(msg.inject_text.text);
|
||||||
LOGW("Could not request 'inject text'");
|
LOGW("Could not request 'inject text'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,22 +396,33 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
input_manager_process_key(struct input_manager *im,
|
input_manager_process_key(struct input_manager *im,
|
||||||
const SDL_KeyboardEvent *event) {
|
const SDL_KeyboardEvent *event) {
|
||||||
// control: indicates the state of the command-line option --no-control
|
// control: indicates the state of the command-line option --no-control
|
||||||
bool control = im->control;
|
bool control = im->control;
|
||||||
|
|
||||||
bool smod = is_shortcut_mod(im, event->keysym.mod);
|
|
||||||
|
|
||||||
struct controller *controller = im->controller;
|
struct controller *controller = im->controller;
|
||||||
|
|
||||||
SDL_Keycode keycode = event->keysym.sym;
|
SDL_Keycode keycode = event->keysym.sym;
|
||||||
|
uint16_t mod = event->keysym.mod;
|
||||||
bool down = event->type == SDL_KEYDOWN;
|
bool down = event->type == SDL_KEYDOWN;
|
||||||
bool ctrl = event->keysym.mod & KMOD_CTRL;
|
bool ctrl = event->keysym.mod & KMOD_CTRL;
|
||||||
bool shift = event->keysym.mod & KMOD_SHIFT;
|
bool shift = event->keysym.mod & KMOD_SHIFT;
|
||||||
bool repeat = event->repeat;
|
bool repeat = event->repeat;
|
||||||
|
|
||||||
|
bool smod = is_shortcut_mod(im, mod);
|
||||||
|
|
||||||
|
if (down && !repeat) {
|
||||||
|
if (keycode == im->last_keycode && mod == im->last_mod) {
|
||||||
|
++im->key_repeat;
|
||||||
|
} else {
|
||||||
|
im->key_repeat = 0;
|
||||||
|
im->last_keycode = keycode;
|
||||||
|
im->last_mod = mod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The shortcut modifier is pressed
|
// The shortcut modifier is pressed
|
||||||
if (smod) {
|
if (smod) {
|
||||||
int action = down ? ACTION_DOWN : ACTION_UP;
|
int action = down ? ACTION_DOWN : ACTION_UP;
|
||||||
@@ -468,17 +521,17 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
return;
|
return;
|
||||||
case SDLK_i:
|
case SDLK_i:
|
||||||
if (!shift && !repeat && down) {
|
if (!shift && !repeat && down) {
|
||||||
struct fps_counter *fps_counter =
|
switch_fps_counter_state(&im->screen->fps_counter);
|
||||||
im->video_buffer->fps_counter;
|
|
||||||
switch_fps_counter_state(fps_counter);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case SDLK_n:
|
case SDLK_n:
|
||||||
if (control && !repeat && down) {
|
if (control && !repeat && down) {
|
||||||
if (shift) {
|
if (shift) {
|
||||||
collapse_notification_panel(controller);
|
collapse_panels(controller);
|
||||||
} else {
|
} else if (im->key_repeat == 0) {
|
||||||
expand_notification_panel(controller);
|
expand_notification_panel(controller);
|
||||||
|
} else {
|
||||||
|
expand_settings_panel(controller);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -539,11 +592,15 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
input_manager_process_mouse_motion(struct input_manager *im,
|
input_manager_process_mouse_motion(struct input_manager *im,
|
||||||
const SDL_MouseMotionEvent *event) {
|
const SDL_MouseMotionEvent *event) {
|
||||||
if (!event->state) {
|
uint32_t mask = SDL_BUTTON_LMASK;
|
||||||
// do not send motion events when no button is pressed
|
if (im->forward_all_clicks) {
|
||||||
|
mask |= SDL_BUTTON_MMASK | SDL_BUTTON_RMASK;
|
||||||
|
}
|
||||||
|
if (!(event->state & mask)) {
|
||||||
|
// do not send motion events when no click is pressed
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event->which == SDL_TOUCH_MOUSEID) {
|
if (event->which == SDL_TOUCH_MOUSEID) {
|
||||||
@@ -593,7 +650,7 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
input_manager_process_touch(struct input_manager *im,
|
input_manager_process_touch(struct input_manager *im,
|
||||||
const SDL_TouchFingerEvent *event) {
|
const SDL_TouchFingerEvent *event) {
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
@@ -625,7 +682,7 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
input_manager_process_mouse_button(struct input_manager *im,
|
input_manager_process_mouse_button(struct input_manager *im,
|
||||||
const SDL_MouseButtonEvent *event) {
|
const SDL_MouseButtonEvent *event) {
|
||||||
bool control = im->control;
|
bool control = im->control;
|
||||||
@@ -636,13 +693,27 @@ input_manager_process_mouse_button(struct input_manager *im,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool down = event->type == SDL_MOUSEBUTTONDOWN;
|
bool down = event->type == SDL_MOUSEBUTTONDOWN;
|
||||||
if (!im->forward_all_clicks && down) {
|
if (!im->forward_all_clicks) {
|
||||||
|
int action = down ? ACTION_DOWN : ACTION_UP;
|
||||||
|
|
||||||
|
if (control && event->button == SDL_BUTTON_X1) {
|
||||||
|
action_app_switch(im->controller, action);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (control && event->button == SDL_BUTTON_X2 && down) {
|
||||||
|
if (event->clicks < 2) {
|
||||||
|
expand_notification_panel(im->controller);
|
||||||
|
} else {
|
||||||
|
expand_settings_panel(im->controller);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (control && event->button == SDL_BUTTON_RIGHT) {
|
if (control && event->button == SDL_BUTTON_RIGHT) {
|
||||||
press_back_or_turn_screen_on(im->controller);
|
press_back_or_turn_screen_on(im->controller, action);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (control && event->button == SDL_BUTTON_MIDDLE) {
|
if (control && event->button == SDL_BUTTON_MIDDLE) {
|
||||||
action_home(im->controller, ACTION_DOWN | ACTION_UP);
|
action_home(im->controller, action);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -655,7 +726,9 @@ input_manager_process_mouse_button(struct input_manager *im,
|
|||||||
bool outside = x < r->x || x >= r->x + r->w
|
bool outside = x < r->x || x >= r->x + r->w
|
||||||
|| y < r->y || y >= r->y + r->h;
|
|| y < r->y || y >= r->y + r->h;
|
||||||
if (outside) {
|
if (outside) {
|
||||||
|
if (down) {
|
||||||
screen_resize_to_fit(im->screen);
|
screen_resize_to_fit(im->screen);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -724,7 +797,7 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
input_manager_process_mouse_wheel(struct input_manager *im,
|
input_manager_process_mouse_wheel(struct input_manager *im,
|
||||||
const SDL_MouseWheelEvent *event) {
|
const SDL_MouseWheelEvent *event) {
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
@@ -734,3 +807,46 @@ input_manager_process_mouse_wheel(struct input_manager *im,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
input_manager_handle_event(struct input_manager *im, SDL_Event *event) {
|
||||||
|
switch (event->type) {
|
||||||
|
case SDL_TEXTINPUT:
|
||||||
|
if (!im->control) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
input_manager_process_text_input(im, &event->text);
|
||||||
|
return true;
|
||||||
|
case SDL_KEYDOWN:
|
||||||
|
case SDL_KEYUP:
|
||||||
|
// some key events do not interact with the device, so process the
|
||||||
|
// event even if control is disabled
|
||||||
|
input_manager_process_key(im, &event->key);
|
||||||
|
return true;
|
||||||
|
case SDL_MOUSEMOTION:
|
||||||
|
if (!im->control) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
input_manager_process_mouse_motion(im, &event->motion);
|
||||||
|
return true;
|
||||||
|
case SDL_MOUSEWHEEL:
|
||||||
|
if (!im->control) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
input_manager_process_mouse_wheel(im, &event->wheel);
|
||||||
|
return true;
|
||||||
|
case SDL_MOUSEBUTTONDOWN:
|
||||||
|
case SDL_MOUSEBUTTONUP:
|
||||||
|
// some mouse events do not interact with the device, so process
|
||||||
|
// the event even if control is disabled
|
||||||
|
input_manager_process_mouse_button(im, &event->button);
|
||||||
|
return true;
|
||||||
|
case SDL_FINGERMOTION:
|
||||||
|
case SDL_FINGERDOWN:
|
||||||
|
case SDL_FINGERUP:
|
||||||
|
input_manager_process_touch(im, &event->tfinger);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
#ifndef INPUTMANAGER_H
|
#ifndef INPUTMANAGER_H
|
||||||
#define INPUTMANAGER_H
|
#define INPUTMANAGER_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "common.h"
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "fps_counter.h"
|
#include "fps_counter.h"
|
||||||
#include "scrcpy.h"
|
#include "scrcpy.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "video_buffer.h"
|
|
||||||
|
|
||||||
struct input_manager {
|
struct input_manager {
|
||||||
struct controller *controller;
|
struct controller *controller;
|
||||||
struct video_buffer *video_buffer;
|
|
||||||
struct screen *screen;
|
struct screen *screen;
|
||||||
|
|
||||||
// SDL reports repeated events as a boolean, but Android expects the actual
|
// SDL reports repeated events as a boolean, but Android expects the actual
|
||||||
@@ -34,34 +32,20 @@ struct input_manager {
|
|||||||
} sdl_shortcut_mods;
|
} sdl_shortcut_mods;
|
||||||
|
|
||||||
bool vfinger_down;
|
bool vfinger_down;
|
||||||
|
|
||||||
|
// Tracks the number of identical consecutive shortcut key down events.
|
||||||
|
// Not to be confused with event->repeat, which counts the number of
|
||||||
|
// system-generated repeated key presses.
|
||||||
|
unsigned key_repeat;
|
||||||
|
SDL_Keycode last_keycode;
|
||||||
|
uint16_t last_mod;
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
input_manager_init(struct input_manager *im,
|
input_manager_init(struct input_manager *im, struct controller *controller,
|
||||||
const struct scrcpy_options *options);
|
struct screen *screen, const struct scrcpy_options *options);
|
||||||
|
|
||||||
void
|
bool
|
||||||
input_manager_process_text_input(struct input_manager *im,
|
input_manager_handle_event(struct input_manager *im, SDL_Event *event);
|
||||||
const SDL_TextInputEvent *event);
|
|
||||||
|
|
||||||
void
|
|
||||||
input_manager_process_key(struct input_manager *im,
|
|
||||||
const SDL_KeyboardEvent *event);
|
|
||||||
|
|
||||||
void
|
|
||||||
input_manager_process_mouse_motion(struct input_manager *im,
|
|
||||||
const SDL_MouseMotionEvent *event);
|
|
||||||
|
|
||||||
void
|
|
||||||
input_manager_process_touch(struct input_manager *im,
|
|
||||||
const SDL_TouchFingerEvent *event);
|
|
||||||
|
|
||||||
void
|
|
||||||
input_manager_process_mouse_button(struct input_manager *im,
|
|
||||||
const SDL_MouseButtonEvent *event);
|
|
||||||
|
|
||||||
void
|
|
||||||
input_manager_process_mouse_wheel(struct input_manager *im,
|
|
||||||
const SDL_MouseWheelEvent *event);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
#include "scrcpy.h"
|
#include "scrcpy.h"
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
# include <libavdevice/avdevice.h>
|
||||||
|
#endif
|
||||||
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
|
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "cli.h"
|
#include "cli.h"
|
||||||
#include "compat.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -28,26 +31,13 @@ print_version(void) {
|
|||||||
fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
|
fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
|
||||||
LIBAVUTIL_VERSION_MINOR,
|
LIBAVUTIL_VERSION_MINOR,
|
||||||
LIBAVUTIL_VERSION_MICRO);
|
LIBAVUTIL_VERSION_MICRO);
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
fprintf(stderr, " - libavdevice %d.%d.%d\n", LIBAVDEVICE_VERSION_MAJOR,
|
||||||
|
LIBAVDEVICE_VERSION_MINOR,
|
||||||
|
LIBAVDEVICE_VERSION_MICRO);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static SDL_LogPriority
|
|
||||||
convert_log_level_to_sdl(enum sc_log_level level) {
|
|
||||||
switch (level) {
|
|
||||||
case SC_LOG_LEVEL_DEBUG:
|
|
||||||
return SDL_LOG_PRIORITY_DEBUG;
|
|
||||||
case SC_LOG_LEVEL_INFO:
|
|
||||||
return SDL_LOG_PRIORITY_INFO;
|
|
||||||
case SC_LOG_LEVEL_WARN:
|
|
||||||
return SDL_LOG_PRIORITY_WARN;
|
|
||||||
case SC_LOG_LEVEL_ERROR:
|
|
||||||
return SDL_LOG_PRIORITY_ERROR;
|
|
||||||
default:
|
|
||||||
assert(!"unexpected log level");
|
|
||||||
return SDL_LOG_PRIORITY_INFO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char *argv[]) {
|
main(int argc, char *argv[]) {
|
||||||
#ifdef __WINDOWS__
|
#ifdef __WINDOWS__
|
||||||
@@ -71,8 +61,7 @@ main(int argc, char *argv[]) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_LogPriority sdl_log = convert_log_level_to_sdl(args.opts.log_level);
|
sc_set_log_level(args.opts.log_level);
|
||||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
|
|
||||||
|
|
||||||
if (args.help) {
|
if (args.help) {
|
||||||
scrcpy_print_usage(argv[0]);
|
scrcpy_print_usage(argv[0]);
|
||||||
@@ -90,6 +79,12 @@ main(int argc, char *argv[]) {
|
|||||||
av_register_all();
|
av_register_all();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
if (args.opts.v4l2_device) {
|
||||||
|
avdevice_register_all();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (avformat_network_init()) {
|
if (avformat_network_init()) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#ifndef SC_OPENGL_H
|
#ifndef SC_OPENGL_H
|
||||||
#define SC_OPENGL_H
|
#define SC_OPENGL_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <SDL2/SDL_opengl.h>
|
#include <SDL2/SDL_opengl.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
struct sc_opengl {
|
struct sc_opengl {
|
||||||
const char *version;
|
const char *version;
|
||||||
bool is_opengles;
|
bool is_opengles;
|
||||||
|
|||||||
@@ -3,14 +3,13 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <SDL2/SDL_clipboard.h>
|
#include <SDL2/SDL_clipboard.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "device_msg.h"
|
#include "device_msg.h"
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
receiver_init(struct receiver *receiver, socket_t control_socket) {
|
receiver_init(struct receiver *receiver, socket_t control_socket) {
|
||||||
if (!(receiver->mutex = SDL_CreateMutex())) {
|
bool ok = sc_mutex_init(&receiver->mutex);
|
||||||
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
receiver->control_socket = control_socket;
|
receiver->control_socket = control_socket;
|
||||||
@@ -19,7 +18,7 @@ receiver_init(struct receiver *receiver, socket_t control_socket) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
receiver_destroy(struct receiver *receiver) {
|
receiver_destroy(struct receiver *receiver) {
|
||||||
SDL_DestroyMutex(receiver->mutex);
|
sc_mutex_destroy(&receiver->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -102,8 +101,9 @@ bool
|
|||||||
receiver_start(struct receiver *receiver) {
|
receiver_start(struct receiver *receiver) {
|
||||||
LOGD("Starting receiver thread");
|
LOGD("Starting receiver thread");
|
||||||
|
|
||||||
receiver->thread = SDL_CreateThread(run_receiver, "receiver", receiver);
|
bool ok = sc_thread_create(&receiver->thread, run_receiver, "receiver",
|
||||||
if (!receiver->thread) {
|
receiver);
|
||||||
|
if (!ok) {
|
||||||
LOGC("Could not start receiver thread");
|
LOGC("Could not start receiver thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -113,5 +113,5 @@ receiver_start(struct receiver *receiver) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
receiver_join(struct receiver *receiver) {
|
receiver_join(struct receiver *receiver) {
|
||||||
SDL_WaitThread(receiver->thread, NULL);
|
sc_thread_join(&receiver->thread, NULL);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
#ifndef RECEIVER_H
|
#ifndef RECEIVER_H
|
||||||
#define RECEIVER_H
|
#define RECEIVER_H
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include "common.h"
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
#include <SDL2/SDL_thread.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
|
||||||
// receive events from the device
|
// receive events from the device
|
||||||
// managed by the controller
|
// managed by the controller
|
||||||
struct receiver {
|
struct receiver {
|
||||||
socket_t control_socket;
|
socket_t control_socket;
|
||||||
SDL_Thread *thread;
|
sc_thread thread;
|
||||||
SDL_mutex *mutex;
|
sc_mutex mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <libavutil/time.h>
|
#include <libavutil/time.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "compat.h"
|
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
#include "util/str_util.h"
|
||||||
|
|
||||||
|
/** Downcast packet_sink to recorder */
|
||||||
|
#define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink)
|
||||||
|
|
||||||
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
||||||
|
|
||||||
@@ -22,24 +23,27 @@ find_muxer(const char *name) {
|
|||||||
#else
|
#else
|
||||||
oformat = av_oformat_next(oformat);
|
oformat = av_oformat_next(oformat);
|
||||||
#endif
|
#endif
|
||||||
// until null or with name "mp4"
|
// until null or containing the requested name
|
||||||
} while (oformat && strcmp(oformat->name, name));
|
} while (oformat && !strlist_contains(oformat->name, ',', name));
|
||||||
return oformat;
|
return oformat;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct record_packet *
|
static struct record_packet *
|
||||||
record_packet_new(const AVPacket *packet) {
|
record_packet_new(const AVPacket *packet) {
|
||||||
struct record_packet *rec = SDL_malloc(sizeof(*rec));
|
struct record_packet *rec = malloc(sizeof(*rec));
|
||||||
if (!rec) {
|
if (!rec) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// av_packet_ref() does not initialize all fields in old FFmpeg versions
|
rec->packet = av_packet_alloc();
|
||||||
// See <https://github.com/Genymobile/scrcpy/issues/707>
|
if (!rec->packet) {
|
||||||
av_init_packet(&rec->packet);
|
free(rec);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (av_packet_ref(&rec->packet, packet)) {
|
if (av_packet_ref(rec->packet, packet)) {
|
||||||
SDL_free(rec);
|
av_packet_free(&rec->packet);
|
||||||
|
free(rec);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return rec;
|
return rec;
|
||||||
@@ -47,8 +51,9 @@ record_packet_new(const AVPacket *packet) {
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
record_packet_delete(struct record_packet *rec) {
|
record_packet_delete(struct record_packet *rec) {
|
||||||
av_packet_unref(&rec->packet);
|
av_packet_unref(rec->packet);
|
||||||
SDL_free(rec);
|
av_packet_free(&rec->packet);
|
||||||
|
free(rec);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -60,50 +65,6 @@ recorder_queue_clear(struct recorder_queue *queue) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
recorder_init(struct recorder *recorder,
|
|
||||||
const char *filename,
|
|
||||||
enum sc_record_format format,
|
|
||||||
struct size declared_frame_size) {
|
|
||||||
recorder->filename = SDL_strdup(filename);
|
|
||||||
if (!recorder->filename) {
|
|
||||||
LOGE("Could not strdup filename");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
recorder->mutex = SDL_CreateMutex();
|
|
||||||
if (!recorder->mutex) {
|
|
||||||
LOGC("Could not create mutex");
|
|
||||||
SDL_free(recorder->filename);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
recorder->queue_cond = SDL_CreateCond();
|
|
||||||
if (!recorder->queue_cond) {
|
|
||||||
LOGC("Could not create cond");
|
|
||||||
SDL_DestroyMutex(recorder->mutex);
|
|
||||||
SDL_free(recorder->filename);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
queue_init(&recorder->queue);
|
|
||||||
recorder->stopped = false;
|
|
||||||
recorder->failed = false;
|
|
||||||
recorder->format = format;
|
|
||||||
recorder->declared_frame_size = declared_frame_size;
|
|
||||||
recorder->header_written = false;
|
|
||||||
recorder->previous = NULL;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
recorder_destroy(struct recorder *recorder) {
|
|
||||||
SDL_DestroyCond(recorder->queue_cond);
|
|
||||||
SDL_DestroyMutex(recorder->mutex);
|
|
||||||
SDL_free(recorder->filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *
|
static const char *
|
||||||
recorder_get_format_name(enum sc_record_format format) {
|
recorder_get_format_name(enum sc_record_format format) {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
@@ -113,88 +74,6 @@ recorder_get_format_name(enum sc_record_format format) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
|
|
||||||
const char *format_name = recorder_get_format_name(recorder->format);
|
|
||||||
assert(format_name);
|
|
||||||
const AVOutputFormat *format = find_muxer(format_name);
|
|
||||||
if (!format) {
|
|
||||||
LOGE("Could not find muxer");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
recorder->ctx = avformat_alloc_context();
|
|
||||||
if (!recorder->ctx) {
|
|
||||||
LOGE("Could not allocate output context");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
|
|
||||||
// returns (on purpose) a pointer-to-const, but AVFormatContext.oformat
|
|
||||||
// still expects a pointer-to-non-const (it has not be updated accordingly)
|
|
||||||
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
|
|
||||||
recorder->ctx->oformat = (AVOutputFormat *) format;
|
|
||||||
|
|
||||||
av_dict_set(&recorder->ctx->metadata, "comment",
|
|
||||||
"Recorded by scrcpy " SCRCPY_VERSION, 0);
|
|
||||||
|
|
||||||
AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec);
|
|
||||||
if (!ostream) {
|
|
||||||
avformat_free_context(recorder->ctx);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
|
|
||||||
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
|
|
||||||
ostream->codecpar->codec_id = input_codec->id;
|
|
||||||
ostream->codecpar->format = AV_PIX_FMT_YUV420P;
|
|
||||||
ostream->codecpar->width = recorder->declared_frame_size.width;
|
|
||||||
ostream->codecpar->height = recorder->declared_frame_size.height;
|
|
||||||
#else
|
|
||||||
ostream->codec->codec_type = AVMEDIA_TYPE_VIDEO;
|
|
||||||
ostream->codec->codec_id = input_codec->id;
|
|
||||||
ostream->codec->pix_fmt = AV_PIX_FMT_YUV420P;
|
|
||||||
ostream->codec->width = recorder->declared_frame_size.width;
|
|
||||||
ostream->codec->height = recorder->declared_frame_size.height;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int ret = avio_open(&recorder->ctx->pb, recorder->filename,
|
|
||||||
AVIO_FLAG_WRITE);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("Failed to open output file: %s", recorder->filename);
|
|
||||||
// ostream will be cleaned up during context cleaning
|
|
||||||
avformat_free_context(recorder->ctx);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGI("Recording started to %s file: %s", format_name, recorder->filename);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
recorder_close(struct recorder *recorder) {
|
|
||||||
if (recorder->header_written) {
|
|
||||||
int ret = av_write_trailer(recorder->ctx);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("Failed to write trailer to %s", recorder->filename);
|
|
||||||
recorder->failed = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// the recorded file is empty
|
|
||||||
recorder->failed = true;
|
|
||||||
}
|
|
||||||
avio_close(recorder->ctx->pb);
|
|
||||||
avformat_free_context(recorder->ctx);
|
|
||||||
|
|
||||||
if (recorder->failed) {
|
|
||||||
LOGE("Recording failed to %s", recorder->filename);
|
|
||||||
} else {
|
|
||||||
const char *format_name = recorder_get_format_name(recorder->format);
|
|
||||||
LOGI("Recording complete to %s file: %s", format_name, recorder->filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
|
recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
|
||||||
AVStream *ostream = recorder->ctx->streams[0];
|
AVStream *ostream = recorder->ctx->streams[0];
|
||||||
@@ -208,13 +87,8 @@ recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
|
|||||||
// copy the first packet to the extra data
|
// copy the first packet to the extra data
|
||||||
memcpy(extradata, packet->data, packet->size);
|
memcpy(extradata, packet->data, packet->size);
|
||||||
|
|
||||||
#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
|
|
||||||
ostream->codecpar->extradata = extradata;
|
ostream->codecpar->extradata = extradata;
|
||||||
ostream->codecpar->extradata_size = packet->size;
|
ostream->codecpar->extradata_size = packet->size;
|
||||||
#else
|
|
||||||
ostream->codec->extradata = extradata;
|
|
||||||
ostream->codec->extradata_size = packet->size;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int ret = avformat_write_header(recorder->ctx, NULL);
|
int ret = avformat_write_header(recorder->ctx, NULL);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@@ -231,7 +105,7 @@ recorder_rescale_packet(struct recorder *recorder, AVPacket *packet) {
|
|||||||
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
|
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
static bool
|
||||||
recorder_write(struct recorder *recorder, AVPacket *packet) {
|
recorder_write(struct recorder *recorder, AVPacket *packet) {
|
||||||
if (!recorder->header_written) {
|
if (!recorder->header_written) {
|
||||||
if (packet->pts != AV_NOPTS_VALUE) {
|
if (packet->pts != AV_NOPTS_VALUE) {
|
||||||
@@ -260,22 +134,22 @@ run_recorder(void *data) {
|
|||||||
struct recorder *recorder = data;
|
struct recorder *recorder = data;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
mutex_lock(recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
|
|
||||||
while (!recorder->stopped && queue_is_empty(&recorder->queue)) {
|
while (!recorder->stopped && queue_is_empty(&recorder->queue)) {
|
||||||
cond_wait(recorder->queue_cond, recorder->mutex);
|
sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if stopped is set, continue to process the remaining events (to
|
// if stopped is set, continue to process the remaining events (to
|
||||||
// finish the recording) before actually stopping
|
// finish the recording) before actually stopping
|
||||||
|
|
||||||
if (recorder->stopped && queue_is_empty(&recorder->queue)) {
|
if (recorder->stopped && queue_is_empty(&recorder->queue)) {
|
||||||
mutex_unlock(recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
struct record_packet *last = recorder->previous;
|
struct record_packet *last = recorder->previous;
|
||||||
if (last) {
|
if (last) {
|
||||||
// assign an arbitrary duration to the last packet
|
// assign an arbitrary duration to the last packet
|
||||||
last->packet.duration = 100000;
|
last->packet->duration = 100000;
|
||||||
bool ok = recorder_write(recorder, &last->packet);
|
bool ok = recorder_write(recorder, last->packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
// failing to write the last frame is not very serious, no
|
// failing to write the last frame is not very serious, no
|
||||||
// future frame may depend on it, so the resulting file
|
// future frame may depend on it, so the resulting file
|
||||||
@@ -290,7 +164,7 @@ run_recorder(void *data) {
|
|||||||
struct record_packet *rec;
|
struct record_packet *rec;
|
||||||
queue_take(&recorder->queue, next, &rec);
|
queue_take(&recorder->queue, next, &rec);
|
||||||
|
|
||||||
mutex_unlock(recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
|
||||||
// recorder->previous is only written from this thread, no need to lock
|
// recorder->previous is only written from this thread, no need to lock
|
||||||
struct record_packet *previous = recorder->previous;
|
struct record_packet *previous = recorder->previous;
|
||||||
@@ -302,25 +176,45 @@ run_recorder(void *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// config packets have no PTS, we must ignore them
|
// config packets have no PTS, we must ignore them
|
||||||
if (rec->packet.pts != AV_NOPTS_VALUE
|
if (rec->packet->pts != AV_NOPTS_VALUE
|
||||||
&& previous->packet.pts != AV_NOPTS_VALUE) {
|
&& previous->packet->pts != AV_NOPTS_VALUE) {
|
||||||
// we now know the duration of the previous packet
|
// we now know the duration of the previous packet
|
||||||
previous->packet.duration = rec->packet.pts - previous->packet.pts;
|
previous->packet->duration =
|
||||||
|
rec->packet->pts - previous->packet->pts;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ok = recorder_write(recorder, &previous->packet);
|
bool ok = recorder_write(recorder, previous->packet);
|
||||||
record_packet_delete(previous);
|
record_packet_delete(previous);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not record packet");
|
LOGE("Could not record packet");
|
||||||
|
|
||||||
mutex_lock(recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
recorder->failed = true;
|
recorder->failed = true;
|
||||||
// discard pending packets
|
// discard pending packets
|
||||||
recorder_queue_clear(&recorder->queue);
|
recorder_queue_clear(&recorder->queue);
|
||||||
mutex_unlock(recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!recorder->failed) {
|
||||||
|
if (recorder->header_written) {
|
||||||
|
int ret = av_write_trailer(recorder->ctx);
|
||||||
|
if (ret < 0) {
|
||||||
|
LOGE("Failed to write trailer to %s", recorder->filename);
|
||||||
|
recorder->failed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// the recorded file is empty
|
||||||
|
recorder->failed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recorder->failed) {
|
||||||
|
LOGE("Recording failed to %s", recorder->filename);
|
||||||
|
} else {
|
||||||
|
const char *format_name = recorder_get_format_name(recorder->format);
|
||||||
|
LOGI("Recording complete to %s file: %s", format_name, recorder->filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGD("Recorder thread ended");
|
LOGD("Recorder thread ended");
|
||||||
@@ -328,53 +222,176 @@ run_recorder(void *data) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
static bool
|
||||||
recorder_start(struct recorder *recorder) {
|
recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
|
||||||
LOGD("Starting recorder thread");
|
bool ok = sc_mutex_init(&recorder->mutex);
|
||||||
|
if (!ok) {
|
||||||
recorder->thread = SDL_CreateThread(run_recorder, "recorder", recorder);
|
LOGC("Could not create mutex");
|
||||||
if (!recorder->thread) {
|
|
||||||
LOGC("Could not start recorder thread");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ok = sc_cond_init(&recorder->queue_cond);
|
||||||
|
if (!ok) {
|
||||||
|
LOGC("Could not create cond");
|
||||||
|
goto error_mutex_destroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue_init(&recorder->queue);
|
||||||
|
recorder->stopped = false;
|
||||||
|
recorder->failed = false;
|
||||||
|
recorder->header_written = false;
|
||||||
|
recorder->previous = NULL;
|
||||||
|
|
||||||
|
const char *format_name = recorder_get_format_name(recorder->format);
|
||||||
|
assert(format_name);
|
||||||
|
const AVOutputFormat *format = find_muxer(format_name);
|
||||||
|
if (!format) {
|
||||||
|
LOGE("Could not find muxer");
|
||||||
|
goto error_cond_destroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
recorder->ctx = avformat_alloc_context();
|
||||||
|
if (!recorder->ctx) {
|
||||||
|
LOGE("Could not allocate output context");
|
||||||
|
goto error_cond_destroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
|
||||||
|
// returns (on purpose) a pointer-to-const, but AVFormatContext.oformat
|
||||||
|
// still expects a pointer-to-non-const (it has not be updated accordingly)
|
||||||
|
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
|
||||||
|
recorder->ctx->oformat = (AVOutputFormat *) format;
|
||||||
|
|
||||||
|
av_dict_set(&recorder->ctx->metadata, "comment",
|
||||||
|
"Recorded by scrcpy " SCRCPY_VERSION, 0);
|
||||||
|
|
||||||
|
AVStream *ostream = avformat_new_stream(recorder->ctx, input_codec);
|
||||||
|
if (!ostream) {
|
||||||
|
goto error_avformat_free_context;
|
||||||
|
}
|
||||||
|
|
||||||
|
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||||
|
ostream->codecpar->codec_id = input_codec->id;
|
||||||
|
ostream->codecpar->format = AV_PIX_FMT_YUV420P;
|
||||||
|
ostream->codecpar->width = recorder->declared_frame_size.width;
|
||||||
|
ostream->codecpar->height = recorder->declared_frame_size.height;
|
||||||
|
|
||||||
|
int ret = avio_open(&recorder->ctx->pb, recorder->filename,
|
||||||
|
AVIO_FLAG_WRITE);
|
||||||
|
if (ret < 0) {
|
||||||
|
LOGE("Failed to open output file: %s", recorder->filename);
|
||||||
|
// ostream will be cleaned up during context cleaning
|
||||||
|
goto error_avformat_free_context;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGD("Starting recorder thread");
|
||||||
|
ok = sc_thread_create(&recorder->thread, run_recorder, "recorder",
|
||||||
|
recorder);
|
||||||
|
if (!ok) {
|
||||||
|
LOGC("Could not start recorder thread");
|
||||||
|
goto error_avio_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGI("Recording started to %s file: %s", format_name, recorder->filename);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
error_avio_close:
|
||||||
|
avio_close(recorder->ctx->pb);
|
||||||
|
error_avformat_free_context:
|
||||||
|
avformat_free_context(recorder->ctx);
|
||||||
|
error_cond_destroy:
|
||||||
|
sc_cond_destroy(&recorder->queue_cond);
|
||||||
|
error_mutex_destroy:
|
||||||
|
sc_mutex_destroy(&recorder->mutex);
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
recorder_stop(struct recorder *recorder) {
|
recorder_close(struct recorder *recorder) {
|
||||||
mutex_lock(recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
recorder->stopped = true;
|
recorder->stopped = true;
|
||||||
cond_signal(recorder->queue_cond);
|
sc_cond_signal(&recorder->queue_cond);
|
||||||
mutex_unlock(recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
|
|
||||||
|
sc_thread_join(&recorder->thread, NULL);
|
||||||
|
|
||||||
|
avio_close(recorder->ctx->pb);
|
||||||
|
avformat_free_context(recorder->ctx);
|
||||||
|
sc_cond_destroy(&recorder->queue_cond);
|
||||||
|
sc_mutex_destroy(&recorder->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static bool
|
||||||
recorder_join(struct recorder *recorder) {
|
|
||||||
SDL_WaitThread(recorder->thread, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
recorder_push(struct recorder *recorder, const AVPacket *packet) {
|
recorder_push(struct recorder *recorder, const AVPacket *packet) {
|
||||||
mutex_lock(recorder->mutex);
|
sc_mutex_lock(&recorder->mutex);
|
||||||
assert(!recorder->stopped);
|
assert(!recorder->stopped);
|
||||||
|
|
||||||
if (recorder->failed) {
|
if (recorder->failed) {
|
||||||
// reject any new packet (this will stop the stream)
|
// reject any new packet (this will stop the stream)
|
||||||
mutex_unlock(recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct record_packet *rec = record_packet_new(packet);
|
struct record_packet *rec = record_packet_new(packet);
|
||||||
if (!rec) {
|
if (!rec) {
|
||||||
LOGC("Could not allocate record packet");
|
LOGC("Could not allocate record packet");
|
||||||
mutex_unlock(recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
queue_push(&recorder->queue, next, rec);
|
queue_push(&recorder->queue, next, rec);
|
||||||
cond_signal(recorder->queue_cond);
|
sc_cond_signal(&recorder->queue_cond);
|
||||||
|
|
||||||
mutex_unlock(recorder->mutex);
|
sc_mutex_unlock(&recorder->mutex);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
recorder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
|
||||||
|
struct recorder *recorder = DOWNCAST(sink);
|
||||||
|
return recorder_open(recorder, codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
recorder_packet_sink_close(struct sc_packet_sink *sink) {
|
||||||
|
struct recorder *recorder = DOWNCAST(sink);
|
||||||
|
recorder_close(recorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
recorder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) {
|
||||||
|
struct recorder *recorder = DOWNCAST(sink);
|
||||||
|
return recorder_push(recorder, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
recorder_init(struct recorder *recorder,
|
||||||
|
const char *filename,
|
||||||
|
enum sc_record_format format,
|
||||||
|
struct size declared_frame_size) {
|
||||||
|
recorder->filename = strdup(filename);
|
||||||
|
if (!recorder->filename) {
|
||||||
|
LOGE("Could not strdup filename");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
recorder->format = format;
|
||||||
|
recorder->declared_frame_size = declared_frame_size;
|
||||||
|
|
||||||
|
static const struct sc_packet_sink_ops ops = {
|
||||||
|
.open = recorder_packet_sink_open,
|
||||||
|
.close = recorder_packet_sink_close,
|
||||||
|
.push = recorder_packet_sink_push,
|
||||||
|
};
|
||||||
|
|
||||||
|
recorder->packet_sink.ops = &ops;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
recorder_destroy(struct recorder *recorder) {
|
||||||
|
free(recorder->filename);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,34 +1,37 @@
|
|||||||
#ifndef RECORDER_H
|
#ifndef RECORDER_H
|
||||||
#define RECORDER_H
|
#define RECORDER_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
#include <SDL2/SDL_thread.h>
|
|
||||||
|
|
||||||
#include "config.h"
|
#include "coords.h"
|
||||||
#include "common.h"
|
|
||||||
#include "scrcpy.h"
|
#include "scrcpy.h"
|
||||||
|
#include "trait/packet_sink.h"
|
||||||
#include "util/queue.h"
|
#include "util/queue.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
|
||||||
struct record_packet {
|
struct record_packet {
|
||||||
AVPacket packet;
|
AVPacket *packet;
|
||||||
struct record_packet *next;
|
struct record_packet *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct recorder_queue QUEUE(struct record_packet);
|
struct recorder_queue QUEUE(struct record_packet);
|
||||||
|
|
||||||
struct recorder {
|
struct recorder {
|
||||||
|
struct sc_packet_sink packet_sink; // packet sink trait
|
||||||
|
|
||||||
char *filename;
|
char *filename;
|
||||||
enum sc_record_format format;
|
enum sc_record_format format;
|
||||||
AVFormatContext *ctx;
|
AVFormatContext *ctx;
|
||||||
struct size declared_frame_size;
|
struct size declared_frame_size;
|
||||||
bool header_written;
|
bool header_written;
|
||||||
|
|
||||||
SDL_Thread *thread;
|
sc_thread thread;
|
||||||
SDL_mutex *mutex;
|
sc_mutex mutex;
|
||||||
SDL_cond *queue_cond;
|
sc_cond queue_cond;
|
||||||
bool stopped; // set on recorder_stop() by the stream reader
|
bool stopped; // set on recorder_close()
|
||||||
bool failed; // set on packet write failure
|
bool failed; // set on packet write failure
|
||||||
struct recorder_queue queue;
|
struct recorder_queue queue;
|
||||||
|
|
||||||
@@ -46,22 +49,4 @@ recorder_init(struct recorder *recorder, const char *filename,
|
|||||||
void
|
void
|
||||||
recorder_destroy(struct recorder *recorder);
|
recorder_destroy(struct recorder *recorder);
|
||||||
|
|
||||||
bool
|
|
||||||
recorder_open(struct recorder *recorder, const AVCodec *input_codec);
|
|
||||||
|
|
||||||
void
|
|
||||||
recorder_close(struct recorder *recorder);
|
|
||||||
|
|
||||||
bool
|
|
||||||
recorder_start(struct recorder *recorder);
|
|
||||||
|
|
||||||
void
|
|
||||||
recorder_stop(struct recorder *recorder);
|
|
||||||
|
|
||||||
void
|
|
||||||
recorder_join(struct recorder *recorder);
|
|
||||||
|
|
||||||
bool
|
|
||||||
recorder_push(struct recorder *recorder, const AVPacket *packet);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
359
app/src/scrcpy.c
359
app/src/scrcpy.c
@@ -13,49 +13,34 @@
|
|||||||
# include <windows.h>
|
# include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "command.h"
|
|
||||||
#include "common.h"
|
|
||||||
#include "compat.h"
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "decoder.h"
|
#include "decoder.h"
|
||||||
#include "device.h"
|
|
||||||
#include "events.h"
|
#include "events.h"
|
||||||
#include "file_handler.h"
|
#include "file_handler.h"
|
||||||
#include "fps_counter.h"
|
|
||||||
#include "input_manager.h"
|
#include "input_manager.h"
|
||||||
#include "recorder.h"
|
#include "recorder.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include "stream.h"
|
#include "stream.h"
|
||||||
#include "tiny_xpm.h"
|
#include "tiny_xpm.h"
|
||||||
#include "video_buffer.h"
|
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
# include "v4l2_sink.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
static struct server server;
|
struct scrcpy {
|
||||||
static struct screen screen = SCREEN_INITIALIZER;
|
struct server server;
|
||||||
static struct fps_counter fps_counter;
|
struct screen screen;
|
||||||
static struct video_buffer video_buffer;
|
struct stream stream;
|
||||||
static struct stream stream;
|
struct decoder decoder;
|
||||||
static struct decoder decoder;
|
struct recorder recorder;
|
||||||
static struct recorder recorder;
|
#ifdef HAVE_V4L2
|
||||||
static struct controller controller;
|
struct sc_v4l2_sink v4l2_sink;
|
||||||
static struct file_handler file_handler;
|
#endif
|
||||||
|
struct controller controller;
|
||||||
static struct input_manager input_manager = {
|
struct file_handler file_handler;
|
||||||
.controller = &controller,
|
struct input_manager input_manager;
|
||||||
.video_buffer = &video_buffer,
|
|
||||||
.screen = &screen,
|
|
||||||
.repeat = 0,
|
|
||||||
|
|
||||||
// initialized later
|
|
||||||
.prefer_text = false,
|
|
||||||
.sdl_shortcut_mods = {
|
|
||||||
.data = {0},
|
|
||||||
.count = 0,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -133,30 +118,6 @@ sdl_init_and_configure(bool display, const char *render_driver,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if defined(__APPLE__) || defined(__WINDOWS__)
|
|
||||||
# define CONTINUOUS_RESIZING_WORKAROUND
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
|
||||||
// On Windows and MacOS, resizing blocks the event loop, so resizing events are
|
|
||||||
// not triggered. As a workaround, handle them in an event handler.
|
|
||||||
//
|
|
||||||
// <https://bugzilla.libsdl.org/show_bug.cgi?id=2077>
|
|
||||||
// <https://stackoverflow.com/a/40693139/1987178>
|
|
||||||
static int
|
|
||||||
event_watcher(void *data, SDL_Event *event) {
|
|
||||||
(void) data;
|
|
||||||
if (event->type == SDL_WINDOWEVENT
|
|
||||||
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
|
|
||||||
// In practice, it seems to always be called from the same thread in
|
|
||||||
// that specific case. Anyway, it's just a workaround.
|
|
||||||
screen_render(&screen, true);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
is_apk(const char *file) {
|
is_apk(const char *file) {
|
||||||
const char *ext = strrchr(file, '.');
|
const char *ext = strrchr(file, '.');
|
||||||
@@ -170,7 +131,8 @@ enum event_result {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static enum event_result
|
static enum event_result
|
||||||
handle_event(SDL_Event *event, const struct scrcpy_options *options) {
|
handle_event(struct scrcpy *s, const struct scrcpy_options *options,
|
||||||
|
SDL_Event *event) {
|
||||||
switch (event->type) {
|
switch (event->type) {
|
||||||
case EVENT_STREAM_STOPPED:
|
case EVENT_STREAM_STOPPED:
|
||||||
LOGD("Video stream stopped");
|
LOGD("Video stream stopped");
|
||||||
@@ -178,81 +140,45 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) {
|
|||||||
case SDL_QUIT:
|
case SDL_QUIT:
|
||||||
LOGD("User requested to quit");
|
LOGD("User requested to quit");
|
||||||
return EVENT_RESULT_STOPPED_BY_USER;
|
return EVENT_RESULT_STOPPED_BY_USER;
|
||||||
case EVENT_NEW_FRAME:
|
|
||||||
if (!screen.has_frame) {
|
|
||||||
screen.has_frame = true;
|
|
||||||
// this is the very first frame, show the window
|
|
||||||
screen_show_window(&screen);
|
|
||||||
}
|
|
||||||
if (!screen_update_frame(&screen, &video_buffer)) {
|
|
||||||
return EVENT_RESULT_CONTINUE;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SDL_WINDOWEVENT:
|
|
||||||
screen_handle_window_event(&screen, &event->window);
|
|
||||||
break;
|
|
||||||
case SDL_TEXTINPUT:
|
|
||||||
if (!options->control) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
input_manager_process_text_input(&input_manager, &event->text);
|
|
||||||
break;
|
|
||||||
case SDL_KEYDOWN:
|
|
||||||
case SDL_KEYUP:
|
|
||||||
// some key events do not interact with the device, so process the
|
|
||||||
// event even if control is disabled
|
|
||||||
input_manager_process_key(&input_manager, &event->key);
|
|
||||||
break;
|
|
||||||
case SDL_MOUSEMOTION:
|
|
||||||
if (!options->control) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
input_manager_process_mouse_motion(&input_manager, &event->motion);
|
|
||||||
break;
|
|
||||||
case SDL_MOUSEWHEEL:
|
|
||||||
if (!options->control) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
input_manager_process_mouse_wheel(&input_manager, &event->wheel);
|
|
||||||
break;
|
|
||||||
case SDL_MOUSEBUTTONDOWN:
|
|
||||||
case SDL_MOUSEBUTTONUP:
|
|
||||||
// some mouse events do not interact with the device, so process
|
|
||||||
// the event even if control is disabled
|
|
||||||
input_manager_process_mouse_button(&input_manager, &event->button);
|
|
||||||
break;
|
|
||||||
case SDL_FINGERMOTION:
|
|
||||||
case SDL_FINGERDOWN:
|
|
||||||
case SDL_FINGERUP:
|
|
||||||
input_manager_process_touch(&input_manager, &event->tfinger);
|
|
||||||
break;
|
|
||||||
case SDL_DROPFILE: {
|
case SDL_DROPFILE: {
|
||||||
if (!options->control) {
|
if (!options->control) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
char *file = strdup(event->drop.file);
|
||||||
|
SDL_free(event->drop.file);
|
||||||
|
if (!file) {
|
||||||
|
LOGW("Could not strdup drop filename\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
file_handler_action_t action;
|
file_handler_action_t action;
|
||||||
if (is_apk(event->drop.file)) {
|
if (is_apk(file)) {
|
||||||
action = ACTION_INSTALL_APK;
|
action = ACTION_INSTALL_APK;
|
||||||
} else {
|
} else {
|
||||||
action = ACTION_PUSH_FILE;
|
action = ACTION_PUSH_FILE;
|
||||||
}
|
}
|
||||||
file_handler_request(&file_handler, action, event->drop.file);
|
file_handler_request(&s->file_handler, action, file);
|
||||||
break;
|
goto end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool consumed = screen_handle_event(&s->screen, event);
|
||||||
|
if (consumed) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
consumed = input_manager_handle_event(&s->input_manager, event);
|
||||||
|
(void) consumed;
|
||||||
|
|
||||||
|
end:
|
||||||
return EVENT_RESULT_CONTINUE;
|
return EVENT_RESULT_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
event_loop(const struct scrcpy_options *options) {
|
event_loop(struct scrcpy *s, const struct scrcpy_options *options) {
|
||||||
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
|
||||||
if (options->display) {
|
|
||||||
SDL_AddEventWatch(event_watcher, NULL);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
while (SDL_WaitEvent(&event)) {
|
while (SDL_WaitEvent(&event)) {
|
||||||
enum event_result result = handle_event(&event, options);
|
enum event_result result = handle_event(s, options, &event);
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case EVENT_RESULT_STOPPED_BY_USER:
|
case EVENT_RESULT_STOPPED_BY_USER:
|
||||||
return true;
|
return true;
|
||||||
@@ -290,37 +216,54 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
|
|||||||
if (priority == 0) {
|
if (priority == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
char *local_fmt = SDL_malloc(strlen(fmt) + 10);
|
|
||||||
|
size_t fmt_len = strlen(fmt);
|
||||||
|
char *local_fmt = malloc(fmt_len + 10);
|
||||||
if (!local_fmt) {
|
if (!local_fmt) {
|
||||||
LOGC("Could not allocate string");
|
LOGC("Could not allocate string");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// strcpy is safe here, the destination is large enough
|
memcpy(local_fmt, "[FFmpeg] ", 9); // do not write the final '\0'
|
||||||
strcpy(local_fmt, "[FFmpeg] ");
|
memcpy(local_fmt + 9, fmt, fmt_len + 1); // include '\0'
|
||||||
strcpy(local_fmt + 9, fmt);
|
|
||||||
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
|
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
|
||||||
SDL_free(local_fmt);
|
free(local_fmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
stream_on_eos(struct stream *stream, void *userdata) {
|
||||||
|
(void) stream;
|
||||||
|
(void) userdata;
|
||||||
|
|
||||||
|
SDL_Event stop_event;
|
||||||
|
stop_event.type = EVENT_STREAM_STOPPED;
|
||||||
|
SDL_PushEvent(&stop_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
scrcpy(const struct scrcpy_options *options) {
|
scrcpy(const struct scrcpy_options *options) {
|
||||||
if (!server_init(&server)) {
|
static struct scrcpy scrcpy;
|
||||||
|
struct scrcpy *s = &scrcpy;
|
||||||
|
|
||||||
|
if (!server_init(&s->server)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
bool server_started = false;
|
bool server_started = false;
|
||||||
bool fps_counter_initialized = false;
|
|
||||||
bool video_buffer_initialized = false;
|
|
||||||
bool file_handler_initialized = false;
|
bool file_handler_initialized = false;
|
||||||
bool recorder_initialized = false;
|
bool recorder_initialized = false;
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
bool v4l2_sink_initialized = false;
|
||||||
|
#endif
|
||||||
bool stream_started = false;
|
bool stream_started = false;
|
||||||
bool controller_initialized = false;
|
bool controller_initialized = false;
|
||||||
bool controller_started = false;
|
bool controller_started = false;
|
||||||
|
bool screen_initialized = false;
|
||||||
|
|
||||||
bool record = !!options->record_filename;
|
bool record = !!options->record_filename;
|
||||||
struct server_params params = {
|
struct server_params params = {
|
||||||
|
.serial = options->serial,
|
||||||
.log_level = options->log_level,
|
.log_level = options->log_level,
|
||||||
.crop = options->crop,
|
.crop = options->crop,
|
||||||
.port_range = options->port_range,
|
.port_range = options->port_range,
|
||||||
@@ -335,8 +278,9 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
.codec_options = options->codec_options,
|
.codec_options = options->codec_options,
|
||||||
.encoder_name = options->encoder_name,
|
.encoder_name = options->encoder_name,
|
||||||
.force_adb_forward = options->force_adb_forward,
|
.force_adb_forward = options->force_adb_forward,
|
||||||
|
.power_off_on_close = options->power_off_on_close,
|
||||||
};
|
};
|
||||||
if (!server_start(&server, options->serial, ¶ms)) {
|
if (!server_start(&s->server, ¶ms)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,76 +291,66 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!server_connect_to(&server)) {
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
char device_name[DEVICE_NAME_FIELD_LENGTH];
|
char device_name[DEVICE_NAME_FIELD_LENGTH];
|
||||||
struct size frame_size;
|
struct size frame_size;
|
||||||
|
|
||||||
// screenrecord does not send frames when the screen content does not
|
if (!server_connect_to(&s->server, device_name, &frame_size)) {
|
||||||
// change therefore, we transmit the screen size before the video stream,
|
|
||||||
// to be able to init the window immediately
|
|
||||||
if (!device_read_info(server.video_socket, device_name, &frame_size)) {
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct decoder *dec = NULL;
|
if (options->display && options->control) {
|
||||||
if (options->display) {
|
if (!file_handler_init(&s->file_handler, s->server.serial,
|
||||||
if (!fps_counter_init(&fps_counter)) {
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
fps_counter_initialized = true;
|
|
||||||
|
|
||||||
if (!video_buffer_init(&video_buffer, &fps_counter,
|
|
||||||
options->render_expired_frames)) {
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
video_buffer_initialized = true;
|
|
||||||
|
|
||||||
if (options->control) {
|
|
||||||
if (!file_handler_init(&file_handler, server.serial,
|
|
||||||
options->push_target)) {
|
options->push_target)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
file_handler_initialized = true;
|
file_handler_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
decoder_init(&decoder, &video_buffer);
|
struct decoder *dec = NULL;
|
||||||
dec = &decoder;
|
bool needs_decoder = options->display;
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
needs_decoder |= !!options->v4l2_device;
|
||||||
|
#endif
|
||||||
|
if (needs_decoder) {
|
||||||
|
decoder_init(&s->decoder);
|
||||||
|
dec = &s->decoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct recorder *rec = NULL;
|
struct recorder *rec = NULL;
|
||||||
if (record) {
|
if (record) {
|
||||||
if (!recorder_init(&recorder,
|
if (!recorder_init(&s->recorder,
|
||||||
options->record_filename,
|
options->record_filename,
|
||||||
options->record_format,
|
options->record_format,
|
||||||
frame_size)) {
|
frame_size)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
rec = &recorder;
|
rec = &s->recorder;
|
||||||
recorder_initialized = true;
|
recorder_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
av_log_set_callback(av_log_callback);
|
av_log_set_callback(av_log_callback);
|
||||||
|
|
||||||
stream_init(&stream, server.video_socket, dec, rec);
|
const struct stream_callbacks stream_cbs = {
|
||||||
|
.on_eos = stream_on_eos,
|
||||||
|
};
|
||||||
|
stream_init(&s->stream, s->server.video_socket, &stream_cbs, NULL);
|
||||||
|
|
||||||
// now we consumed the header values, the socket receives the video stream
|
if (dec) {
|
||||||
// start the stream
|
stream_add_sink(&s->stream, &dec->packet_sink);
|
||||||
if (!stream_start(&stream)) {
|
}
|
||||||
goto end;
|
|
||||||
|
if (rec) {
|
||||||
|
stream_add_sink(&s->stream, &rec->packet_sink);
|
||||||
}
|
}
|
||||||
stream_started = true;
|
|
||||||
|
|
||||||
if (options->display) {
|
if (options->display) {
|
||||||
if (options->control) {
|
if (options->control) {
|
||||||
if (!controller_init(&controller, server.control_socket)) {
|
if (!controller_init(&s->controller, s->server.control_socket)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
controller_initialized = true;
|
controller_initialized = true;
|
||||||
|
|
||||||
if (!controller_start(&controller)) {
|
if (!controller_start(&s->controller)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
controller_started = true;
|
controller_started = true;
|
||||||
@@ -425,89 +359,120 @@ scrcpy(const struct scrcpy_options *options) {
|
|||||||
const char *window_title =
|
const char *window_title =
|
||||||
options->window_title ? options->window_title : device_name;
|
options->window_title ? options->window_title : device_name;
|
||||||
|
|
||||||
if (!screen_init_rendering(&screen, window_title, frame_size,
|
struct screen_params screen_params = {
|
||||||
options->always_on_top, options->window_x,
|
.window_title = window_title,
|
||||||
options->window_y, options->window_width,
|
.frame_size = frame_size,
|
||||||
options->window_height,
|
.always_on_top = options->always_on_top,
|
||||||
options->window_borderless,
|
.window_x = options->window_x,
|
||||||
options->rotation, options->mipmaps)) {
|
.window_y = options->window_y,
|
||||||
|
.window_width = options->window_width,
|
||||||
|
.window_height = options->window_height,
|
||||||
|
.window_borderless = options->window_borderless,
|
||||||
|
.rotation = options->rotation,
|
||||||
|
.mipmaps = options->mipmaps,
|
||||||
|
.fullscreen = options->fullscreen,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!screen_init(&s->screen, &screen_params)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
screen_initialized = true;
|
||||||
|
|
||||||
|
decoder_add_sink(&s->decoder, &s->screen.frame_sink);
|
||||||
|
|
||||||
if (options->turn_screen_off) {
|
if (options->turn_screen_off) {
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
|
msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
|
||||||
msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF;
|
msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF;
|
||||||
|
|
||||||
if (!controller_push_msg(&controller, &msg)) {
|
if (!controller_push_msg(&s->controller, &msg)) {
|
||||||
LOGW("Could not request 'set screen power mode'");
|
LOGW("Could not request 'set screen power mode'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options->fullscreen) {
|
|
||||||
screen_switch_fullscreen(&screen);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input_manager_init(&input_manager, options);
|
#ifdef HAVE_V4L2
|
||||||
|
if (options->v4l2_device) {
|
||||||
|
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, frame_size)) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
ret = event_loop(options);
|
decoder_add_sink(&s->decoder, &s->v4l2_sink.frame_sink);
|
||||||
|
|
||||||
|
v4l2_sink_initialized = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// now we consumed the header values, the socket receives the video stream
|
||||||
|
// start the stream
|
||||||
|
if (!stream_start(&s->stream)) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
stream_started = true;
|
||||||
|
|
||||||
|
input_manager_init(&s->input_manager, &s->controller, &s->screen, options);
|
||||||
|
|
||||||
|
ret = event_loop(s, options);
|
||||||
LOGD("quit...");
|
LOGD("quit...");
|
||||||
|
|
||||||
screen_destroy(&screen);
|
// Close the window immediately on closing, because screen_destroy() may
|
||||||
|
// only be called once the stream thread is joined (it may take time)
|
||||||
|
screen_hide_window(&s->screen);
|
||||||
|
|
||||||
end:
|
end:
|
||||||
// stop stream and controller so that they don't continue once their socket
|
// The stream is not stopped explicitly, because it will stop by itself on
|
||||||
// is shutdown
|
// end-of-stream
|
||||||
if (stream_started) {
|
|
||||||
stream_stop(&stream);
|
|
||||||
}
|
|
||||||
if (controller_started) {
|
if (controller_started) {
|
||||||
controller_stop(&controller);
|
controller_stop(&s->controller);
|
||||||
}
|
}
|
||||||
if (file_handler_initialized) {
|
if (file_handler_initialized) {
|
||||||
file_handler_stop(&file_handler);
|
file_handler_stop(&s->file_handler);
|
||||||
}
|
}
|
||||||
if (fps_counter_initialized) {
|
if (screen_initialized) {
|
||||||
fps_counter_interrupt(&fps_counter);
|
screen_interrupt(&s->screen);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (server_started) {
|
if (server_started) {
|
||||||
// shutdown the sockets and kill the server
|
// shutdown the sockets and kill the server
|
||||||
server_stop(&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
|
||||||
// interrupted, we can join them
|
// interrupted, we can join them
|
||||||
if (stream_started) {
|
if (stream_started) {
|
||||||
stream_join(&stream);
|
stream_join(&s->stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_V4L2
|
||||||
|
if (v4l2_sink_initialized) {
|
||||||
|
sc_v4l2_sink_destroy(&s->v4l2_sink);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Destroy the screen only after the stream is guaranteed to be finished,
|
||||||
|
// because otherwise the screen could receive new frames after destruction
|
||||||
|
if (screen_initialized) {
|
||||||
|
screen_join(&s->screen);
|
||||||
|
screen_destroy(&s->screen);
|
||||||
|
}
|
||||||
|
|
||||||
if (controller_started) {
|
if (controller_started) {
|
||||||
controller_join(&controller);
|
controller_join(&s->controller);
|
||||||
}
|
}
|
||||||
if (controller_initialized) {
|
if (controller_initialized) {
|
||||||
controller_destroy(&controller);
|
controller_destroy(&s->controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recorder_initialized) {
|
if (recorder_initialized) {
|
||||||
recorder_destroy(&recorder);
|
recorder_destroy(&s->recorder);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file_handler_initialized) {
|
if (file_handler_initialized) {
|
||||||
file_handler_join(&file_handler);
|
file_handler_join(&s->file_handler);
|
||||||
file_handler_destroy(&file_handler);
|
file_handler_destroy(&s->file_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (video_buffer_initialized) {
|
server_destroy(&s->server);
|
||||||
video_buffer_destroy(&video_buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fps_counter_initialized) {
|
|
||||||
fps_counter_join(&fps_counter);
|
|
||||||
fps_counter_destroy(&fps_counter);
|
|
||||||
}
|
|
||||||
|
|
||||||
server_destroy(&server);
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
#ifndef SCRCPY_H
|
#ifndef SCRCPY_H
|
||||||
#define SCRCPY_H
|
#define SCRCPY_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
enum sc_log_level {
|
enum sc_log_level {
|
||||||
|
SC_LOG_LEVEL_VERBOSE,
|
||||||
SC_LOG_LEVEL_DEBUG,
|
SC_LOG_LEVEL_DEBUG,
|
||||||
SC_LOG_LEVEL_INFO,
|
SC_LOG_LEVEL_INFO,
|
||||||
SC_LOG_LEVEL_WARN,
|
SC_LOG_LEVEL_WARN,
|
||||||
@@ -20,6 +21,16 @@ enum sc_record_format {
|
|||||||
SC_RECORD_FORMAT_MKV,
|
SC_RECORD_FORMAT_MKV,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum sc_lock_video_orientation {
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
|
||||||
|
// lock the current orientation when scrcpy starts
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_1,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_2,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_3,
|
||||||
|
};
|
||||||
|
|
||||||
#define SC_MAX_SHORTCUT_MODS 8
|
#define SC_MAX_SHORTCUT_MODS 8
|
||||||
|
|
||||||
enum sc_shortcut_mod {
|
enum sc_shortcut_mod {
|
||||||
@@ -52,6 +63,7 @@ struct scrcpy_options {
|
|||||||
const char *render_driver;
|
const char *render_driver;
|
||||||
const char *codec_options;
|
const char *codec_options;
|
||||||
const char *encoder_name;
|
const char *encoder_name;
|
||||||
|
const char *v4l2_device;
|
||||||
enum sc_log_level log_level;
|
enum sc_log_level log_level;
|
||||||
enum sc_record_format record_format;
|
enum sc_record_format record_format;
|
||||||
struct sc_port_range port_range;
|
struct sc_port_range port_range;
|
||||||
@@ -59,20 +71,19 @@ struct scrcpy_options {
|
|||||||
uint16_t max_size;
|
uint16_t max_size;
|
||||||
uint32_t bit_rate;
|
uint32_t bit_rate;
|
||||||
uint16_t max_fps;
|
uint16_t max_fps;
|
||||||
int8_t lock_video_orientation;
|
enum sc_lock_video_orientation lock_video_orientation;
|
||||||
uint8_t rotation;
|
uint8_t rotation;
|
||||||
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
||||||
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
||||||
uint16_t window_width;
|
uint16_t window_width;
|
||||||
uint16_t window_height;
|
uint16_t window_height;
|
||||||
uint16_t display_id;
|
uint32_t display_id;
|
||||||
bool show_touches;
|
bool show_touches;
|
||||||
bool fullscreen;
|
bool fullscreen;
|
||||||
bool always_on_top;
|
bool always_on_top;
|
||||||
bool control;
|
bool control;
|
||||||
bool display;
|
bool display;
|
||||||
bool turn_screen_off;
|
bool turn_screen_off;
|
||||||
bool render_expired_frames;
|
|
||||||
bool prefer_text;
|
bool prefer_text;
|
||||||
bool window_borderless;
|
bool window_borderless;
|
||||||
bool mipmaps;
|
bool mipmaps;
|
||||||
@@ -82,6 +93,7 @@ struct scrcpy_options {
|
|||||||
bool forward_key_repeat;
|
bool forward_key_repeat;
|
||||||
bool forward_all_clicks;
|
bool forward_all_clicks;
|
||||||
bool legacy_paste;
|
bool legacy_paste;
|
||||||
|
bool power_off_on_close;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SCRCPY_OPTIONS_DEFAULT { \
|
#define SCRCPY_OPTIONS_DEFAULT { \
|
||||||
@@ -93,6 +105,7 @@ struct scrcpy_options {
|
|||||||
.render_driver = NULL, \
|
.render_driver = NULL, \
|
||||||
.codec_options = NULL, \
|
.codec_options = NULL, \
|
||||||
.encoder_name = NULL, \
|
.encoder_name = NULL, \
|
||||||
|
.v4l2_device = NULL, \
|
||||||
.log_level = SC_LOG_LEVEL_INFO, \
|
.log_level = SC_LOG_LEVEL_INFO, \
|
||||||
.record_format = SC_RECORD_FORMAT_AUTO, \
|
.record_format = SC_RECORD_FORMAT_AUTO, \
|
||||||
.port_range = { \
|
.port_range = { \
|
||||||
@@ -103,10 +116,10 @@ struct scrcpy_options {
|
|||||||
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
|
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
|
||||||
.count = 2, \
|
.count = 2, \
|
||||||
}, \
|
}, \
|
||||||
.max_size = DEFAULT_MAX_SIZE, \
|
.max_size = 0, \
|
||||||
.bit_rate = DEFAULT_BIT_RATE, \
|
.bit_rate = DEFAULT_BIT_RATE, \
|
||||||
.max_fps = 0, \
|
.max_fps = 0, \
|
||||||
.lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \
|
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED, \
|
||||||
.rotation = 0, \
|
.rotation = 0, \
|
||||||
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
|
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
|
||||||
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
|
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
|
||||||
@@ -119,7 +132,6 @@ struct scrcpy_options {
|
|||||||
.control = true, \
|
.control = true, \
|
||||||
.display = true, \
|
.display = true, \
|
||||||
.turn_screen_off = false, \
|
.turn_screen_off = false, \
|
||||||
.render_expired_frames = false, \
|
|
||||||
.prefer_text = false, \
|
.prefer_text = false, \
|
||||||
.window_borderless = false, \
|
.window_borderless = false, \
|
||||||
.mipmaps = true, \
|
.mipmaps = true, \
|
||||||
@@ -129,6 +141,7 @@ struct scrcpy_options {
|
|||||||
.forward_key_repeat = true, \
|
.forward_key_repeat = true, \
|
||||||
.forward_all_clicks = false, \
|
.forward_all_clicks = false, \
|
||||||
.legacy_paste = false, \
|
.legacy_paste = false, \
|
||||||
|
.power_off_on_close = false, \
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|||||||
320
app/src/screen.c
320
app/src/screen.c
@@ -4,18 +4,17 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "events.h"
|
||||||
#include "common.h"
|
|
||||||
#include "compat.h"
|
|
||||||
#include "icon.xpm"
|
#include "icon.xpm"
|
||||||
#include "scrcpy.h"
|
#include "scrcpy.h"
|
||||||
#include "tiny_xpm.h"
|
#include "tiny_xpm.h"
|
||||||
#include "video_buffer.h"
|
#include "video_buffer.h"
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
#define DISPLAY_MARGINS 96
|
#define DISPLAY_MARGINS 96
|
||||||
|
|
||||||
|
#define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink)
|
||||||
|
|
||||||
static inline struct size
|
static inline struct size
|
||||||
get_rotated_size(struct size size, int rotation) {
|
get_rotated_size(struct size size, int rotation) {
|
||||||
struct size rotated_size;
|
struct size rotated_size;
|
||||||
@@ -42,6 +41,18 @@ get_window_size(const struct screen *screen) {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct point
|
||||||
|
get_window_position(const struct screen *screen) {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
SDL_GetWindowPosition(screen->window, &x, &y);
|
||||||
|
|
||||||
|
struct point point;
|
||||||
|
point.x = x;
|
||||||
|
point.y = y;
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
|
||||||
// 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 size new_size) {
|
set_window_size(struct screen *screen, struct size new_size) {
|
||||||
@@ -123,13 +134,6 @@ get_optimal_size(struct size current_size, struct size content_size) {
|
|||||||
return window_size;
|
return window_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// same as get_optimal_size(), but read the current size from the window
|
|
||||||
static inline struct size
|
|
||||||
get_optimal_window_size(const struct screen *screen, struct size content_size) {
|
|
||||||
struct size window_size = get_window_size(screen);
|
|
||||||
return get_optimal_size(window_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 size
|
static inline struct size
|
||||||
@@ -194,11 +198,6 @@ screen_update_content_rect(struct screen *screen) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
screen_init(struct screen *screen) {
|
|
||||||
*screen = (struct screen) SCREEN_INITIALIZER;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
||||||
@@ -226,27 +225,112 @@ create_texture(struct screen *screen) {
|
|||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
#if defined(__APPLE__) || defined(__WINDOWS__)
|
||||||
screen_init_rendering(struct screen *screen, const char *window_title,
|
# define CONTINUOUS_RESIZING_WORKAROUND
|
||||||
struct size frame_size, bool always_on_top,
|
#endif
|
||||||
int16_t window_x, int16_t window_y, uint16_t window_width,
|
|
||||||
uint16_t window_height, bool window_borderless,
|
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
||||||
uint8_t rotation, bool mipmaps) {
|
// On Windows and MacOS, resizing blocks the event loop, so resizing events are
|
||||||
screen->frame_size = frame_size;
|
// not triggered. As a workaround, handle them in an event handler.
|
||||||
screen->rotation = rotation;
|
//
|
||||||
if (rotation) {
|
// <https://bugzilla.libsdl.org/show_bug.cgi?id=2077>
|
||||||
LOGI("Initial display rotation set to %u", rotation);
|
// <https://stackoverflow.com/a/40693139/1987178>
|
||||||
|
static int
|
||||||
|
event_watcher(void *data, SDL_Event *event) {
|
||||||
|
struct screen *screen = data;
|
||||||
|
if (event->type == SDL_WINDOWEVENT
|
||||||
|
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||||
|
// In practice, it seems to always be called from the same thread in
|
||||||
|
// that specific case. Anyway, it's just a workaround.
|
||||||
|
screen_render(screen, true);
|
||||||
}
|
}
|
||||||
struct size content_size = get_rotated_size(frame_size, screen->rotation);
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool
|
||||||
|
screen_frame_sink_open(struct sc_frame_sink *sink) {
|
||||||
|
struct screen *screen = DOWNCAST(sink);
|
||||||
|
(void) screen;
|
||||||
|
#ifndef NDEBUG
|
||||||
|
screen->open = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// nothing to do, the screen is already open on the main thread
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
screen_frame_sink_close(struct sc_frame_sink *sink) {
|
||||||
|
struct screen *screen = DOWNCAST(sink);
|
||||||
|
(void) screen;
|
||||||
|
#ifndef NDEBUG
|
||||||
|
screen->open = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// nothing to do, the screen lifecycle is not managed by the frame producer
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||||
|
struct screen *screen = DOWNCAST(sink);
|
||||||
|
|
||||||
|
bool previous_frame_skipped;
|
||||||
|
bool ok = video_buffer_push(&screen->vb, frame, &previous_frame_skipped);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previous_frame_skipped) {
|
||||||
|
fps_counter_add_skipped_frame(&screen->fps_counter);
|
||||||
|
// The EVENT_NEW_FRAME triggered for the previous frame will consume
|
||||||
|
// this new frame instead
|
||||||
|
} else {
|
||||||
|
static SDL_Event new_frame_event = {
|
||||||
|
.type = EVENT_NEW_FRAME,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Post the event on the UI thread
|
||||||
|
SDL_PushEvent(&new_frame_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
screen_init(struct screen *screen, const struct screen_params *params) {
|
||||||
|
screen->resize_pending = false;
|
||||||
|
screen->has_frame = false;
|
||||||
|
screen->fullscreen = false;
|
||||||
|
screen->maximized = false;
|
||||||
|
|
||||||
|
bool ok = video_buffer_init(&screen->vb);
|
||||||
|
if (!ok) {
|
||||||
|
LOGE("Could not initialize video buffer");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fps_counter_init(&screen->fps_counter)) {
|
||||||
|
LOGE("Could not initialize FPS counter");
|
||||||
|
goto error_destroy_video_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
screen->frame_size = params->frame_size;
|
||||||
|
screen->rotation = params->rotation;
|
||||||
|
if (screen->rotation) {
|
||||||
|
LOGI("Initial display rotation set to %u", screen->rotation);
|
||||||
|
}
|
||||||
|
struct size content_size =
|
||||||
|
get_rotated_size(screen->frame_size, screen->rotation);
|
||||||
screen->content_size = content_size;
|
screen->content_size = content_size;
|
||||||
|
|
||||||
struct size window_size =
|
struct size window_size = get_initial_optimal_size(content_size,
|
||||||
get_initial_optimal_size(content_size, window_width, window_height);
|
params->window_width,
|
||||||
uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
|
params->window_height);
|
||||||
#ifdef HIDPI_SUPPORT
|
uint32_t window_flags = SDL_WINDOW_HIDDEN
|
||||||
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
|
| SDL_WINDOW_RESIZABLE
|
||||||
#endif
|
| SDL_WINDOW_ALLOW_HIGHDPI;
|
||||||
if (always_on_top) {
|
if (params->always_on_top) {
|
||||||
#ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
|
#ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
|
||||||
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
||||||
#else
|
#else
|
||||||
@@ -254,28 +338,27 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
|||||||
"(compile with SDL >= 2.0.5 to enable it)");
|
"(compile with SDL >= 2.0.5 to enable it)");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
if (window_borderless) {
|
if (params->window_borderless) {
|
||||||
window_flags |= SDL_WINDOW_BORDERLESS;
|
window_flags |= SDL_WINDOW_BORDERLESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
int x = window_x != SC_WINDOW_POSITION_UNDEFINED
|
int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED
|
||||||
? window_x : (int) SDL_WINDOWPOS_UNDEFINED;
|
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED;
|
||||||
int y = window_y != SC_WINDOW_POSITION_UNDEFINED
|
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED
|
||||||
? window_y : (int) SDL_WINDOWPOS_UNDEFINED;
|
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED;
|
||||||
screen->window = SDL_CreateWindow(window_title, x, y,
|
screen->window = SDL_CreateWindow(params->window_title, x, y,
|
||||||
window_size.width, window_size.height,
|
window_size.width, window_size.height,
|
||||||
window_flags);
|
window_flags);
|
||||||
if (!screen->window) {
|
if (!screen->window) {
|
||||||
LOGC("Could not create window: %s", SDL_GetError());
|
LOGC("Could not create window: %s", SDL_GetError());
|
||||||
return false;
|
goto error_destroy_fps_counter;
|
||||||
}
|
}
|
||||||
|
|
||||||
screen->renderer = SDL_CreateRenderer(screen->window, -1,
|
screen->renderer = SDL_CreateRenderer(screen->window, -1,
|
||||||
SDL_RENDERER_ACCELERATED);
|
SDL_RENDERER_ACCELERATED);
|
||||||
if (!screen->renderer) {
|
if (!screen->renderer) {
|
||||||
LOGC("Could not create renderer: %s", SDL_GetError());
|
LOGC("Could not create renderer: %s", SDL_GetError());
|
||||||
screen_destroy(screen);
|
goto error_destroy_window;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_RendererInfo renderer_info;
|
SDL_RendererInfo renderer_info;
|
||||||
@@ -283,15 +366,17 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
|||||||
const char *renderer_name = r ? NULL : renderer_info.name;
|
const char *renderer_name = r ? NULL : renderer_info.name;
|
||||||
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
||||||
|
|
||||||
|
screen->mipmaps = false;
|
||||||
|
|
||||||
// starts with "opengl"
|
// starts with "opengl"
|
||||||
screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||||
if (screen->use_opengl) {
|
if (use_opengl) {
|
||||||
struct sc_opengl *gl = &screen->gl;
|
struct sc_opengl *gl = &screen->gl;
|
||||||
sc_opengl_init(gl);
|
sc_opengl_init(gl);
|
||||||
|
|
||||||
LOGI("OpenGL version: %s", gl->version);
|
LOGI("OpenGL version: %s", gl->version);
|
||||||
|
|
||||||
if (mipmaps) {
|
if (params->mipmaps) {
|
||||||
bool supports_mipmaps =
|
bool supports_mipmaps =
|
||||||
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
|
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
|
||||||
2, 0 /* OpenGL ES 2.0+ */);
|
2, 0 /* OpenGL ES 2.0+ */);
|
||||||
@@ -305,7 +390,7 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
|||||||
} else {
|
} else {
|
||||||
LOGI("Trilinear filtering disabled");
|
LOGI("Trilinear filtering disabled");
|
||||||
}
|
}
|
||||||
} else {
|
} else if (params->mipmaps) {
|
||||||
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,13 +402,18 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
|||||||
LOGW("Could not load icon");
|
LOGW("Could not load icon");
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width,
|
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, params->frame_size.width,
|
||||||
frame_size.height);
|
params->frame_size.height);
|
||||||
screen->texture = create_texture(screen);
|
screen->texture = create_texture(screen);
|
||||||
if (!screen->texture) {
|
if (!screen->texture) {
|
||||||
LOGC("Could not create texture: %s", SDL_GetError());
|
LOGC("Could not create texture: %s", SDL_GetError());
|
||||||
screen_destroy(screen);
|
goto error_destroy_renderer;
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
screen->frame = av_frame_alloc();
|
||||||
|
if (!screen->frame) {
|
||||||
|
LOGC("Could not create screen frame");
|
||||||
|
goto error_destroy_texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the window size to trigger a SIZE_CHANGED event, to workaround
|
// Reset the window size to trigger a SIZE_CHANGED event, to workaround
|
||||||
@@ -333,25 +423,73 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
|||||||
|
|
||||||
screen_update_content_rect(screen);
|
screen_update_content_rect(screen);
|
||||||
|
|
||||||
|
if (params->fullscreen) {
|
||||||
|
screen_switch_fullscreen(screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONTINUOUS_RESIZING_WORKAROUND
|
||||||
|
SDL_AddEventWatch(event_watcher, screen);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const struct sc_frame_sink_ops ops = {
|
||||||
|
.open = screen_frame_sink_open,
|
||||||
|
.close = screen_frame_sink_close,
|
||||||
|
.push = screen_frame_sink_push,
|
||||||
|
};
|
||||||
|
|
||||||
|
screen->frame_sink.ops = &ops;
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
screen->open = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
error_destroy_texture:
|
||||||
|
SDL_DestroyTexture(screen->texture);
|
||||||
|
error_destroy_renderer:
|
||||||
|
SDL_DestroyRenderer(screen->renderer);
|
||||||
|
error_destroy_window:
|
||||||
|
SDL_DestroyWindow(screen->window);
|
||||||
|
error_destroy_fps_counter:
|
||||||
|
fps_counter_destroy(&screen->fps_counter);
|
||||||
|
error_destroy_video_buffer:
|
||||||
|
video_buffer_destroy(&screen->vb);
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
screen_show_window(struct screen *screen) {
|
screen_show_window(struct screen *screen) {
|
||||||
SDL_ShowWindow(screen->window);
|
SDL_ShowWindow(screen->window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
screen_hide_window(struct screen *screen) {
|
||||||
|
SDL_HideWindow(screen->window);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
screen_interrupt(struct screen *screen) {
|
||||||
|
fps_counter_interrupt(&screen->fps_counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
screen_join(struct screen *screen) {
|
||||||
|
fps_counter_join(&screen->fps_counter);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
screen_destroy(struct screen *screen) {
|
screen_destroy(struct screen *screen) {
|
||||||
if (screen->texture) {
|
#ifndef NDEBUG
|
||||||
|
assert(!screen->open);
|
||||||
|
#endif
|
||||||
|
av_frame_free(&screen->frame);
|
||||||
SDL_DestroyTexture(screen->texture);
|
SDL_DestroyTexture(screen->texture);
|
||||||
}
|
|
||||||
if (screen->renderer) {
|
|
||||||
SDL_DestroyRenderer(screen->renderer);
|
SDL_DestroyRenderer(screen->renderer);
|
||||||
}
|
|
||||||
if (screen->window) {
|
|
||||||
SDL_DestroyWindow(screen->window);
|
SDL_DestroyWindow(screen->window);
|
||||||
}
|
fps_counter_destroy(&screen->fps_counter);
|
||||||
|
video_buffer_destroy(&screen->vb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -448,24 +586,25 @@ update_texture(struct screen *screen, const AVFrame *frame) {
|
|||||||
frame->data[2], frame->linesize[2]);
|
frame->data[2], frame->linesize[2]);
|
||||||
|
|
||||||
if (screen->mipmaps) {
|
if (screen->mipmaps) {
|
||||||
assert(screen->use_opengl);
|
|
||||||
SDL_GL_BindTexture(screen->texture, NULL, NULL);
|
SDL_GL_BindTexture(screen->texture, NULL, NULL);
|
||||||
screen->gl.GenerateMipmap(GL_TEXTURE_2D);
|
screen->gl.GenerateMipmap(GL_TEXTURE_2D);
|
||||||
SDL_GL_UnbindTexture(screen->texture);
|
SDL_GL_UnbindTexture(screen->texture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
static bool
|
||||||
screen_update_frame(struct screen *screen, struct video_buffer *vb) {
|
screen_update_frame(struct screen *screen) {
|
||||||
mutex_lock(vb->mutex);
|
av_frame_unref(screen->frame);
|
||||||
const AVFrame *frame = video_buffer_consume_rendered_frame(vb);
|
video_buffer_consume(&screen->vb, screen->frame);
|
||||||
|
AVFrame *frame = screen->frame;
|
||||||
|
|
||||||
|
fps_counter_add_rendered_frame(&screen->fps_counter);
|
||||||
|
|
||||||
struct 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)) {
|
||||||
mutex_unlock(vb->mutex);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
update_texture(screen, frame);
|
update_texture(screen, frame);
|
||||||
mutex_unlock(vb->mutex);
|
|
||||||
|
|
||||||
screen_render(screen, false);
|
screen_render(screen, false);
|
||||||
return true;
|
return true;
|
||||||
@@ -528,9 +667,20 @@ screen_resize_to_fit(struct screen *screen) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct point point = get_window_position(screen);
|
||||||
|
struct size window_size = get_window_size(screen);
|
||||||
|
|
||||||
struct size optimal_size =
|
struct size optimal_size =
|
||||||
get_optimal_window_size(screen, screen->content_size);
|
get_optimal_size(window_size, screen->content_size);
|
||||||
|
|
||||||
|
// Center the window related to the device screen
|
||||||
|
assert(optimal_size.width <= window_size.width);
|
||||||
|
assert(optimal_size.height <= window_size.height);
|
||||||
|
uint32_t new_x = point.x + (window_size.width - optimal_size.width) / 2;
|
||||||
|
uint32_t new_y = point.y + (window_size.height - optimal_size.height) / 2;
|
||||||
|
|
||||||
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
|
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
|
||||||
|
SDL_SetWindowPosition(screen->window, new_x, new_y);
|
||||||
LOGD("Resized to optimal size: %ux%u", optimal_size.width,
|
LOGD("Resized to optimal size: %ux%u", optimal_size.width,
|
||||||
optimal_size.height);
|
optimal_size.height);
|
||||||
}
|
}
|
||||||
@@ -552,10 +702,26 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
|
|||||||
content_size.height);
|
content_size.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool
|
||||||
screen_handle_window_event(struct screen *screen,
|
screen_handle_event(struct screen *screen, SDL_Event *event) {
|
||||||
const SDL_WindowEvent *event) {
|
switch (event->type) {
|
||||||
switch (event->event) {
|
case EVENT_NEW_FRAME:
|
||||||
|
if (!screen->has_frame) {
|
||||||
|
screen->has_frame = true;
|
||||||
|
// this is the very first frame, show the window
|
||||||
|
screen_show_window(screen);
|
||||||
|
}
|
||||||
|
bool ok = screen_update_frame(screen);
|
||||||
|
if (!ok) {
|
||||||
|
LOGW("Frame update failed\n");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case SDL_WINDOWEVENT:
|
||||||
|
if (!screen->has_frame) {
|
||||||
|
// Do nothing
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
switch (event->window.event) {
|
||||||
case SDL_WINDOWEVENT_EXPOSED:
|
case SDL_WINDOWEVENT_EXPOSED:
|
||||||
screen_render(screen, true);
|
screen_render(screen, true);
|
||||||
break;
|
break;
|
||||||
@@ -567,16 +733,22 @@ screen_handle_window_event(struct screen *screen,
|
|||||||
break;
|
break;
|
||||||
case SDL_WINDOWEVENT_RESTORED:
|
case SDL_WINDOWEVENT_RESTORED:
|
||||||
if (screen->fullscreen) {
|
if (screen->fullscreen) {
|
||||||
// On Windows, in maximized+fullscreen, disabling fullscreen
|
// On Windows, in maximized+fullscreen, disabling
|
||||||
// mode unexpectedly triggers the "restored" then "maximized"
|
// fullscreen mode unexpectedly triggers the "restored"
|
||||||
// events, leaving the window in a weird state (maximized
|
// then "maximized" events, leaving the window in a
|
||||||
// according to the events, but not maximized visually).
|
// weird state (maximized according to the events, but
|
||||||
|
// not maximized visually).
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
screen->maximized = false;
|
screen->maximized = false;
|
||||||
apply_pending_resize(screen);
|
apply_pending_resize(screen);
|
||||||
|
screen_render(screen, true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct point
|
struct point
|
||||||
|
|||||||
104
app/src/screen.h
104
app/src/screen.h
@@ -1,21 +1,30 @@
|
|||||||
#ifndef SCREEN_H
|
#ifndef SCREEN_H
|
||||||
#define SCREEN_H
|
#define SCREEN_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "coords.h"
|
||||||
#include "common.h"
|
|
||||||
#include "opengl.h"
|
#include "opengl.h"
|
||||||
|
#include "trait/frame_sink.h"
|
||||||
struct video_buffer;
|
#include "video_buffer.h"
|
||||||
|
|
||||||
struct screen {
|
struct screen {
|
||||||
|
struct sc_frame_sink frame_sink; // frame sink trait
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
bool open; // track the open/close state to assert correct behavior
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct video_buffer vb;
|
||||||
|
struct fps_counter fps_counter;
|
||||||
|
|
||||||
SDL_Window *window;
|
SDL_Window *window;
|
||||||
SDL_Renderer *renderer;
|
SDL_Renderer *renderer;
|
||||||
SDL_Texture *texture;
|
SDL_Texture *texture;
|
||||||
bool use_opengl;
|
|
||||||
struct sc_opengl gl;
|
struct sc_opengl gl;
|
||||||
struct size frame_size;
|
struct size frame_size;
|
||||||
struct size content_size; // rotated frame_size
|
struct size content_size; // rotated frame_size
|
||||||
@@ -32,67 +41,52 @@ struct screen {
|
|||||||
bool has_frame;
|
bool has_frame;
|
||||||
bool fullscreen;
|
bool fullscreen;
|
||||||
bool maximized;
|
bool maximized;
|
||||||
bool no_window;
|
|
||||||
bool mipmaps;
|
bool mipmaps;
|
||||||
|
|
||||||
|
AVFrame *frame;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SCREEN_INITIALIZER { \
|
struct screen_params {
|
||||||
.window = NULL, \
|
const char *window_title;
|
||||||
.renderer = NULL, \
|
struct size frame_size;
|
||||||
.texture = NULL, \
|
bool always_on_top;
|
||||||
.use_opengl = false, \
|
|
||||||
.gl = {0}, \
|
|
||||||
.frame_size = { \
|
|
||||||
.width = 0, \
|
|
||||||
.height = 0, \
|
|
||||||
}, \
|
|
||||||
.content_size = { \
|
|
||||||
.width = 0, \
|
|
||||||
.height = 0, \
|
|
||||||
}, \
|
|
||||||
.resize_pending = false, \
|
|
||||||
.windowed_content_size = { \
|
|
||||||
.width = 0, \
|
|
||||||
.height = 0, \
|
|
||||||
}, \
|
|
||||||
.rotation = 0, \
|
|
||||||
.rect = { \
|
|
||||||
.x = 0, \
|
|
||||||
.y = 0, \
|
|
||||||
.w = 0, \
|
|
||||||
.h = 0, \
|
|
||||||
}, \
|
|
||||||
.has_frame = false, \
|
|
||||||
.fullscreen = false, \
|
|
||||||
.maximized = false, \
|
|
||||||
.no_window = false, \
|
|
||||||
.mipmaps = false, \
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize default values
|
int16_t window_x;
|
||||||
void
|
int16_t window_y;
|
||||||
screen_init(struct screen *screen);
|
uint16_t window_width; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||||
|
uint16_t window_height; // accepts SC_WINDOW_POSITION_UNDEFINED
|
||||||
|
|
||||||
|
bool window_borderless;
|
||||||
|
|
||||||
|
uint8_t rotation;
|
||||||
|
bool mipmaps;
|
||||||
|
|
||||||
|
bool fullscreen;
|
||||||
|
};
|
||||||
|
|
||||||
// initialize screen, create window, renderer and texture (window is hidden)
|
// initialize screen, create window, renderer and texture (window is hidden)
|
||||||
// window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED
|
|
||||||
bool
|
bool
|
||||||
screen_init_rendering(struct screen *screen, const char *window_title,
|
screen_init(struct screen *screen, const struct screen_params *params);
|
||||||
struct size frame_size, bool always_on_top,
|
|
||||||
int16_t window_x, int16_t window_y, uint16_t window_width,
|
|
||||||
uint16_t window_height, bool window_borderless,
|
|
||||||
uint8_t rotation, bool mipmaps);
|
|
||||||
|
|
||||||
// show the window
|
// request to interrupt any inner thread
|
||||||
|
// must be called before screen_join()
|
||||||
void
|
void
|
||||||
screen_show_window(struct screen *screen);
|
screen_interrupt(struct screen *screen);
|
||||||
|
|
||||||
|
// join any inner thread
|
||||||
|
void
|
||||||
|
screen_join(struct screen *screen);
|
||||||
|
|
||||||
// destroy window, renderer and texture (if any)
|
// destroy window, renderer and texture (if any)
|
||||||
void
|
void
|
||||||
screen_destroy(struct screen *screen);
|
screen_destroy(struct screen *screen);
|
||||||
|
|
||||||
// resize if necessary and write the rendered frame into the texture
|
// hide the window
|
||||||
bool
|
//
|
||||||
screen_update_frame(struct screen *screen, struct video_buffer *vb);
|
// It is used to hide the window immediately on closing without waiting for
|
||||||
|
// screen_destroy()
|
||||||
|
void
|
||||||
|
screen_hide_window(struct screen *screen);
|
||||||
|
|
||||||
// render the texture to the renderer
|
// render the texture to the renderer
|
||||||
//
|
//
|
||||||
@@ -117,9 +111,9 @@ screen_resize_to_pixel_perfect(struct screen *screen);
|
|||||||
void
|
void
|
||||||
screen_set_rotation(struct screen *screen, unsigned rotation);
|
screen_set_rotation(struct screen *screen, unsigned rotation);
|
||||||
|
|
||||||
// react to window events
|
// react to SDL events
|
||||||
void
|
bool
|
||||||
screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
|
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
|
||||||
|
|||||||
151
app/src/server.c
151
app/src/server.c
@@ -5,13 +5,10 @@
|
|||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <SDL2/SDL_thread.h>
|
|
||||||
#include <SDL2/SDL_timer.h>
|
#include <SDL2/SDL_timer.h>
|
||||||
#include <SDL2/SDL_platform.h>
|
#include <SDL2/SDL_platform.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "adb.h"
|
||||||
#include "command.h"
|
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
#include "util/str_util.h"
|
#include "util/str_util.h"
|
||||||
@@ -34,7 +31,7 @@ get_server_path(void) {
|
|||||||
#ifdef __WINDOWS__
|
#ifdef __WINDOWS__
|
||||||
char *server_path = utf8_from_wide_char(server_path_env);
|
char *server_path = utf8_from_wide_char(server_path_env);
|
||||||
#else
|
#else
|
||||||
char *server_path = SDL_strdup(server_path_env);
|
char *server_path = strdup(server_path_env);
|
||||||
#endif
|
#endif
|
||||||
if (!server_path) {
|
if (!server_path) {
|
||||||
LOGE("Could not allocate memory");
|
LOGE("Could not allocate memory");
|
||||||
@@ -46,7 +43,7 @@ get_server_path(void) {
|
|||||||
|
|
||||||
#ifndef PORTABLE
|
#ifndef PORTABLE
|
||||||
LOGD("Using server: " DEFAULT_SERVER_PATH);
|
LOGD("Using server: " DEFAULT_SERVER_PATH);
|
||||||
char *server_path = SDL_strdup(DEFAULT_SERVER_PATH);
|
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;
|
||||||
@@ -61,19 +58,19 @@ get_server_path(void) {
|
|||||||
LOGE("Could not get executable path, "
|
LOGE("Could not get executable path, "
|
||||||
"using " SERVER_FILENAME " from current directory");
|
"using " SERVER_FILENAME " from current directory");
|
||||||
// not found, use current directory
|
// not found, use current directory
|
||||||
return SERVER_FILENAME;
|
return strdup(SERVER_FILENAME);
|
||||||
}
|
}
|
||||||
char *dir = dirname(executable_path);
|
char *dir = dirname(executable_path);
|
||||||
size_t dirlen = strlen(dir);
|
size_t dirlen = strlen(dir);
|
||||||
|
|
||||||
// sizeof(SERVER_FILENAME) gives statically the size including the null byte
|
// sizeof(SERVER_FILENAME) gives statically the size including the null byte
|
||||||
size_t len = dirlen + 1 + sizeof(SERVER_FILENAME);
|
size_t len = dirlen + 1 + sizeof(SERVER_FILENAME);
|
||||||
char *server_path = SDL_malloc(len);
|
char *server_path = malloc(len);
|
||||||
if (!server_path) {
|
if (!server_path) {
|
||||||
LOGE("Could not alloc server path string, "
|
LOGE("Could not alloc server path string, "
|
||||||
"using " SERVER_FILENAME " from current directory");
|
"using " SERVER_FILENAME " from current directory");
|
||||||
SDL_free(executable_path);
|
free(executable_path);
|
||||||
return SERVER_FILENAME;
|
return strdup(SERVER_FILENAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(server_path, dir, dirlen);
|
memcpy(server_path, dir, dirlen);
|
||||||
@@ -81,7 +78,7 @@ get_server_path(void) {
|
|||||||
memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME));
|
memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME));
|
||||||
// the final null byte has been copied with SERVER_FILENAME
|
// the final null byte has been copied with SERVER_FILENAME
|
||||||
|
|
||||||
SDL_free(executable_path);
|
free(executable_path);
|
||||||
|
|
||||||
LOGD("Using server (portable): %s", server_path);
|
LOGD("Using server (portable): %s", server_path);
|
||||||
return server_path;
|
return server_path;
|
||||||
@@ -96,36 +93,36 @@ push_server(const char *serial) {
|
|||||||
}
|
}
|
||||||
if (!is_regular_file(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);
|
||||||
SDL_free(server_path);
|
free(server_path);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH);
|
process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH);
|
||||||
SDL_free(server_path);
|
free(server_path);
|
||||||
return process_check_success(process, "adb push");
|
return process_check_success(process, "adb push", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
enable_tunnel_reverse(const char *serial, uint16_t local_port) {
|
enable_tunnel_reverse(const char *serial, uint16_t local_port) {
|
||||||
process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
|
process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
|
||||||
return process_check_success(process, "adb reverse");
|
return process_check_success(process, "adb reverse", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
disable_tunnel_reverse(const char *serial) {
|
disable_tunnel_reverse(const char *serial) {
|
||||||
process_t process = adb_reverse_remove(serial, SOCKET_NAME);
|
process_t process = adb_reverse_remove(serial, SOCKET_NAME);
|
||||||
return process_check_success(process, "adb reverse --remove");
|
return process_check_success(process, "adb reverse --remove", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
enable_tunnel_forward(const char *serial, uint16_t local_port) {
|
enable_tunnel_forward(const char *serial, uint16_t local_port) {
|
||||||
process_t process = adb_forward(serial, local_port, SOCKET_NAME);
|
process_t process = adb_forward(serial, local_port, SOCKET_NAME);
|
||||||
return process_check_success(process, "adb forward");
|
return process_check_success(process, "adb forward", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
disable_tunnel_forward(const char *serial, uint16_t local_port) {
|
disable_tunnel_forward(const char *serial, uint16_t local_port) {
|
||||||
process_t process = adb_forward_remove(serial, local_port);
|
process_t process = adb_forward_remove(serial, local_port);
|
||||||
return process_check_success(process, "adb forward --remove");
|
return process_check_success(process, "adb forward --remove", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@@ -238,6 +235,8 @@ enable_tunnel_any_port(struct server *server, struct sc_port_range port_range,
|
|||||||
static const char *
|
static const char *
|
||||||
log_level_to_server_string(enum sc_log_level level) {
|
log_level_to_server_string(enum sc_log_level level) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
|
case SC_LOG_LEVEL_VERBOSE:
|
||||||
|
return "verbose";
|
||||||
case SC_LOG_LEVEL_DEBUG:
|
case SC_LOG_LEVEL_DEBUG:
|
||||||
return "debug";
|
return "debug";
|
||||||
case SC_LOG_LEVEL_INFO:
|
case SC_LOG_LEVEL_INFO:
|
||||||
@@ -258,12 +257,12 @@ execute_server(struct server *server, const struct server_params *params) {
|
|||||||
char bit_rate_string[11];
|
char bit_rate_string[11];
|
||||||
char max_fps_string[6];
|
char max_fps_string[6];
|
||||||
char lock_video_orientation_string[5];
|
char lock_video_orientation_string[5];
|
||||||
char display_id_string[6];
|
char display_id_string[11];
|
||||||
sprintf(max_size_string, "%"PRIu16, params->max_size);
|
sprintf(max_size_string, "%"PRIu16, params->max_size);
|
||||||
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
|
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
|
||||||
sprintf(max_fps_string, "%"PRIu16, params->max_fps);
|
sprintf(max_fps_string, "%"PRIu16, params->max_fps);
|
||||||
sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation);
|
sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation);
|
||||||
sprintf(display_id_string, "%"PRIu16, params->display_id);
|
sprintf(display_id_string, "%"PRIu32, params->display_id);
|
||||||
const char *const cmd[] = {
|
const char *const cmd[] = {
|
||||||
"shell",
|
"shell",
|
||||||
"CLASSPATH=" DEVICE_SERVER_PATH,
|
"CLASSPATH=" DEVICE_SERVER_PATH,
|
||||||
@@ -296,6 +295,7 @@ execute_server(struct server *server, const struct server_params *params) {
|
|||||||
params->stay_awake ? "true" : "false",
|
params->stay_awake ? "true" : "false",
|
||||||
params->codec_options ? params->codec_options : "-",
|
params->codec_options ? params->codec_options : "-",
|
||||||
params->encoder_name ? params->encoder_name : "-",
|
params->encoder_name ? params->encoder_name : "-",
|
||||||
|
params->power_off_on_close ? "true" : "false",
|
||||||
};
|
};
|
||||||
#ifdef SERVER_DEBUGGER
|
#ifdef SERVER_DEBUGGER
|
||||||
LOGI("Server debugger waiting for a client on device port "
|
LOGI("Server debugger waiting for a client on device port "
|
||||||
@@ -308,7 +308,7 @@ execute_server(struct server *server, const struct server_params *params) {
|
|||||||
// Port: 5005
|
// Port: 5005
|
||||||
// Then click on "Debug"
|
// Then click on "Debug"
|
||||||
#endif
|
#endif
|
||||||
return adb_execute(server->serial, cmd, sizeof(cmd) / sizeof(cmd[0]));
|
return adb_execute(server->serial, cmd, ARRAY_LEN(cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
static socket_t
|
static socket_t
|
||||||
@@ -358,18 +358,17 @@ bool
|
|||||||
server_init(struct server *server) {
|
server_init(struct server *server) {
|
||||||
server->serial = NULL;
|
server->serial = NULL;
|
||||||
server->process = PROCESS_NONE;
|
server->process = PROCESS_NONE;
|
||||||
server->wait_server_thread = NULL;
|
|
||||||
atomic_flag_clear_explicit(&server->server_socket_closed,
|
atomic_flag_clear_explicit(&server->server_socket_closed,
|
||||||
memory_order_relaxed);
|
memory_order_relaxed);
|
||||||
|
|
||||||
server->mutex = SDL_CreateMutex();
|
bool ok = sc_mutex_init(&server->mutex);
|
||||||
if (!server->mutex) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
server->process_terminated_cond = SDL_CreateCond();
|
ok = sc_cond_init(&server->process_terminated_cond);
|
||||||
if (!server->process_terminated_cond) {
|
if (!ok) {
|
||||||
SDL_DestroyMutex(server->mutex);
|
sc_mutex_destroy(&server->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,8 +378,6 @@ server_init(struct server *server) {
|
|||||||
server->video_socket = INVALID_SOCKET;
|
server->video_socket = INVALID_SOCKET;
|
||||||
server->control_socket = INVALID_SOCKET;
|
server->control_socket = INVALID_SOCKET;
|
||||||
|
|
||||||
server->port_range.first = 0;
|
|
||||||
server->port_range.last = 0;
|
|
||||||
server->local_port = 0;
|
server->local_port = 0;
|
||||||
|
|
||||||
server->tunnel_enabled = false;
|
server->tunnel_enabled = false;
|
||||||
@@ -392,12 +389,12 @@ server_init(struct server *server) {
|
|||||||
static int
|
static int
|
||||||
run_wait_server(void *data) {
|
run_wait_server(void *data) {
|
||||||
struct server *server = data;
|
struct server *server = data;
|
||||||
cmd_simple_wait(server->process, NULL); // ignore exit code
|
process_wait(server->process, false); // ignore exit code
|
||||||
|
|
||||||
mutex_lock(server->mutex);
|
sc_mutex_lock(&server->mutex);
|
||||||
server->process_terminated = true;
|
server->process_terminated = true;
|
||||||
cond_signal(server->process_terminated_cond);
|
sc_cond_signal(&server->process_terminated_cond);
|
||||||
mutex_unlock(server->mutex);
|
sc_mutex_unlock(&server->mutex);
|
||||||
|
|
||||||
// no need for synchronization, server_socket is initialized before this
|
// no need for synchronization, server_socket is initialized before this
|
||||||
// thread was created
|
// thread was created
|
||||||
@@ -412,30 +409,28 @@ run_wait_server(void *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
server_start(struct server *server, const char *serial,
|
server_start(struct server *server, const struct server_params *params) {
|
||||||
const struct server_params *params) {
|
if (params->serial) {
|
||||||
server->port_range = params->port_range;
|
server->serial = strdup(params->serial);
|
||||||
|
|
||||||
if (serial) {
|
|
||||||
server->serial = SDL_strdup(serial);
|
|
||||||
if (!server->serial) {
|
if (!server->serial) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!push_server(serial)) {
|
if (!push_server(params->serial)) {
|
||||||
goto error1;
|
/* server->serial will be freed on server_destroy() */
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!enable_tunnel_any_port(server, params->port_range,
|
if (!enable_tunnel_any_port(server, params->port_range,
|
||||||
params->force_adb_forward)) {
|
params->force_adb_forward)) {
|
||||||
goto error1;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// server will connect to our server socket
|
// server will connect to our server socket
|
||||||
server->process = execute_server(server, params);
|
server->process = execute_server(server, params);
|
||||||
if (server->process == PROCESS_NONE) {
|
if (server->process == PROCESS_NONE) {
|
||||||
goto error2;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the server process dies before connecting to the server socket, then
|
// If the server process dies before connecting to the server socket, then
|
||||||
@@ -444,19 +439,19 @@ server_start(struct server *server, const char *serial,
|
|||||||
// things simple and multiplatform, just spawn a new thread waiting for the
|
// things simple and multiplatform, just spawn a new thread waiting for the
|
||||||
// server process and calling shutdown()/close() on the server socket if
|
// server process and calling shutdown()/close() on the server socket if
|
||||||
// necessary to wake up any accept() blocking call.
|
// necessary to wake up any accept() blocking call.
|
||||||
server->wait_server_thread =
|
bool ok = sc_thread_create(&server->wait_server_thread, run_wait_server,
|
||||||
SDL_CreateThread(run_wait_server, "wait-server", server);
|
"wait-server", server);
|
||||||
if (!server->wait_server_thread) {
|
if (!ok) {
|
||||||
cmd_terminate(server->process);
|
process_terminate(server->process);
|
||||||
cmd_simple_wait(server->process, NULL); // ignore exit code
|
process_wait(server->process, true); // ignore exit code
|
||||||
goto error2;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
server->tunnel_enabled = true;
|
server->tunnel_enabled = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
error2:
|
error:
|
||||||
if (!server->tunnel_forward) {
|
if (!server->tunnel_forward) {
|
||||||
bool was_closed =
|
bool was_closed =
|
||||||
atomic_flag_test_and_set(&server->server_socket_closed);
|
atomic_flag_test_and_set(&server->server_socket_closed);
|
||||||
@@ -466,13 +461,32 @@ error2:
|
|||||||
close_socket(server->server_socket);
|
close_socket(server->server_socket);
|
||||||
}
|
}
|
||||||
disable_tunnel(server);
|
disable_tunnel(server);
|
||||||
error1:
|
|
||||||
SDL_free(server->serial);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
device_read_info(socket_t device_socket, char *device_name, struct size *size) {
|
||||||
|
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
|
||||||
|
int r = net_recv_all(device_socket, buf, sizeof(buf));
|
||||||
|
if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
|
||||||
|
LOGE("Could not retrieve device information");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 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
|
bool
|
||||||
server_connect_to(struct server *server) {
|
server_connect_to(struct server *server, char *device_name, struct size *size) {
|
||||||
if (!server->tunnel_forward) {
|
if (!server->tunnel_forward) {
|
||||||
server->video_socket = net_accept(server->server_socket);
|
server->video_socket = net_accept(server->server_socket);
|
||||||
if (server->video_socket == INVALID_SOCKET) {
|
if (server->video_socket == INVALID_SOCKET) {
|
||||||
@@ -512,7 +526,8 @@ server_connect_to(struct server *server) {
|
|||||||
disable_tunnel(server); // ignore failure
|
disable_tunnel(server); // ignore failure
|
||||||
server->tunnel_enabled = false;
|
server->tunnel_enabled = false;
|
||||||
|
|
||||||
return true;
|
// The sockets will be closed on stop if device_read_info() fails
|
||||||
|
return device_read_info(server->video_socket, device_name, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -536,33 +551,33 @@ server_stop(struct server *server) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Give some delay for the server to terminate properly
|
// Give some delay for the server to terminate properly
|
||||||
mutex_lock(server->mutex);
|
sc_mutex_lock(&server->mutex);
|
||||||
int r = 0;
|
bool signaled = false;
|
||||||
if (!server->process_terminated) {
|
if (!server->process_terminated) {
|
||||||
#define WATCHDOG_DELAY_MS 1000
|
#define WATCHDOG_DELAY_MS 1000
|
||||||
r = cond_wait_timeout(server->process_terminated_cond,
|
signaled = sc_cond_timedwait(&server->process_terminated_cond,
|
||||||
server->mutex,
|
&server->mutex,
|
||||||
WATCHDOG_DELAY_MS);
|
WATCHDOG_DELAY_MS);
|
||||||
}
|
}
|
||||||
mutex_unlock(server->mutex);
|
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 (r == SDL_MUTEX_TIMEDOUT) {
|
if (!signaled) {
|
||||||
// FIXME There is a race condition here: there is a small chance that
|
// The process is terminated, but not reaped (closed) yet, so its PID
|
||||||
// the process is already terminated, and the PID assigned to a new
|
// is still valid.
|
||||||
// process.
|
|
||||||
LOGW("Killing the server...");
|
LOGW("Killing the server...");
|
||||||
cmd_terminate(server->process);
|
process_terminate(server->process);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_WaitThread(server->wait_server_thread, NULL);
|
sc_thread_join(&server->wait_server_thread, NULL);
|
||||||
|
process_close(server->process);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
server_destroy(struct server *server) {
|
server_destroy(struct server *server) {
|
||||||
SDL_free(server->serial);
|
free(server->serial);
|
||||||
SDL_DestroyCond(server->process_terminated_cond);
|
sc_cond_destroy(&server->process_terminated_cond);
|
||||||
SDL_DestroyMutex(server->mutex);
|
sc_mutex_destroy(&server->mutex);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,39 @@
|
|||||||
#ifndef SERVER_H
|
#ifndef SERVER_H
|
||||||
#define SERVER_H
|
#define SERVER_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <SDL2/SDL_thread.h>
|
|
||||||
|
|
||||||
#include "config.h"
|
#include "adb.h"
|
||||||
#include "command.h"
|
#include "coords.h"
|
||||||
#include "common.h"
|
|
||||||
#include "scrcpy.h"
|
#include "scrcpy.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
|
||||||
struct server {
|
struct server {
|
||||||
char *serial;
|
char *serial;
|
||||||
process_t process;
|
process_t process;
|
||||||
SDL_Thread *wait_server_thread;
|
sc_thread wait_server_thread;
|
||||||
atomic_flag server_socket_closed;
|
atomic_flag server_socket_closed;
|
||||||
|
|
||||||
SDL_mutex *mutex;
|
sc_mutex mutex;
|
||||||
SDL_cond *process_terminated_cond;
|
sc_cond process_terminated_cond;
|
||||||
bool process_terminated;
|
bool process_terminated;
|
||||||
|
|
||||||
socket_t server_socket; // only used if !tunnel_forward
|
socket_t server_socket; // only used if !tunnel_forward
|
||||||
socket_t video_socket;
|
socket_t video_socket;
|
||||||
socket_t control_socket;
|
socket_t control_socket;
|
||||||
struct sc_port_range port_range;
|
|
||||||
uint16_t local_port; // selected from port_range
|
uint16_t local_port; // selected from port_range
|
||||||
bool tunnel_enabled;
|
bool tunnel_enabled;
|
||||||
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
|
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
|
||||||
};
|
};
|
||||||
|
|
||||||
struct server_params {
|
struct server_params {
|
||||||
|
const char *serial;
|
||||||
enum sc_log_level log_level;
|
enum sc_log_level log_level;
|
||||||
const char *crop;
|
const char *crop;
|
||||||
const char *codec_options;
|
const char *codec_options;
|
||||||
@@ -43,10 +44,11 @@ struct server_params {
|
|||||||
uint16_t max_fps;
|
uint16_t max_fps;
|
||||||
int8_t lock_video_orientation;
|
int8_t lock_video_orientation;
|
||||||
bool control;
|
bool control;
|
||||||
uint16_t display_id;
|
uint32_t display_id;
|
||||||
bool show_touches;
|
bool show_touches;
|
||||||
bool stay_awake;
|
bool stay_awake;
|
||||||
bool force_adb_forward;
|
bool force_adb_forward;
|
||||||
|
bool power_off_on_close;
|
||||||
};
|
};
|
||||||
|
|
||||||
// init default values
|
// init default values
|
||||||
@@ -55,12 +57,13 @@ server_init(struct server *server);
|
|||||||
|
|
||||||
// push, enable tunnel et start the server
|
// push, enable tunnel et start the server
|
||||||
bool
|
bool
|
||||||
server_start(struct server *server, const char *serial,
|
server_start(struct server *server, const struct server_params *params);
|
||||||
const struct server_params *params);
|
|
||||||
|
|
||||||
|
#define DEVICE_NAME_FIELD_LENGTH 64
|
||||||
// block until the communication with the server is established
|
// 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
|
bool
|
||||||
server_connect_to(struct server *server);
|
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
|
||||||
|
|||||||
198
app/src/stream.c
198
app/src/stream.c
@@ -3,13 +3,8 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
#include <libavutil/time.h>
|
#include <libavutil/time.h>
|
||||||
#include <SDL2/SDL_events.h>
|
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
#include <SDL2/SDL_thread.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "compat.h"
|
|
||||||
#include "decoder.h"
|
#include "decoder.h"
|
||||||
#include "events.h"
|
#include "events.h"
|
||||||
#include "recorder.h"
|
#include "recorder.h"
|
||||||
@@ -62,33 +57,12 @@ stream_recv_packet(struct stream *stream, AVPacket *packet) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
notify_stopped(void) {
|
|
||||||
SDL_Event stop_event;
|
|
||||||
stop_event.type = EVENT_STREAM_STOPPED;
|
|
||||||
SDL_PushEvent(&stop_event);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
process_config_packet(struct stream *stream, AVPacket *packet) {
|
push_packet_to_sinks(struct stream *stream, const AVPacket *packet) {
|
||||||
if (stream->recorder && !recorder_push(stream->recorder, packet)) {
|
for (unsigned i = 0; i < stream->sink_count; ++i) {
|
||||||
LOGE("Could not send config packet to recorder");
|
struct sc_packet_sink *sink = stream->sinks[i];
|
||||||
return false;
|
if (!sink->ops->push(sink, packet)) {
|
||||||
}
|
LOGE("Could not send config packet to sink %d", i);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
process_frame(struct stream *stream, AVPacket *packet) {
|
|
||||||
if (stream->decoder && !decoder_push(stream->decoder, packet)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stream->recorder) {
|
|
||||||
packet->dts = packet->pts;
|
|
||||||
|
|
||||||
if (!recorder_push(stream->recorder, packet)) {
|
|
||||||
LOGE("Could not send packet to recorder");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,9 +89,11 @@ stream_parse(struct stream *stream, AVPacket *packet) {
|
|||||||
packet->flags |= AV_PKT_FLAG_KEY;
|
packet->flags |= AV_PKT_FLAG_KEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ok = process_frame(stream, packet);
|
packet->dts = packet->pts;
|
||||||
|
|
||||||
|
bool ok = push_packet_to_sinks(stream, packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGE("Could not process frame");
|
LOGE("Could not process packet");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,39 +104,44 @@ static bool
|
|||||||
stream_push_packet(struct stream *stream, AVPacket *packet) {
|
stream_push_packet(struct stream *stream, AVPacket *packet) {
|
||||||
bool is_config = packet->pts == AV_NOPTS_VALUE;
|
bool is_config = packet->pts == AV_NOPTS_VALUE;
|
||||||
|
|
||||||
// A config packet must not be decoded immetiately (it contains no
|
// A config packet must not be decoded immediately (it contains no
|
||||||
// frame); instead, it must be concatenated with the future data packet.
|
// frame); instead, it must be concatenated with the future data packet.
|
||||||
if (stream->has_pending || is_config) {
|
if (stream->pending || is_config) {
|
||||||
size_t offset;
|
size_t offset;
|
||||||
if (stream->has_pending) {
|
if (stream->pending) {
|
||||||
offset = stream->pending.size;
|
offset = stream->pending->size;
|
||||||
if (av_grow_packet(&stream->pending, packet->size)) {
|
if (av_grow_packet(stream->pending, packet->size)) {
|
||||||
LOGE("Could not grow packet");
|
LOGE("Could not grow packet");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
offset = 0;
|
offset = 0;
|
||||||
if (av_new_packet(&stream->pending, packet->size)) {
|
stream->pending = av_packet_alloc();
|
||||||
LOGE("Could not create packet");
|
if (!stream->pending) {
|
||||||
|
LOGE("Could not allocate packet");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (av_new_packet(stream->pending, packet->size)) {
|
||||||
|
LOGE("Could not create packet");
|
||||||
|
av_packet_free(&stream->pending);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
stream->has_pending = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(stream->pending.data + offset, packet->data, packet->size);
|
memcpy(stream->pending->data + offset, packet->data, packet->size);
|
||||||
|
|
||||||
if (!is_config) {
|
if (!is_config) {
|
||||||
// prepare the concat packet to send to the decoder
|
// prepare the concat packet to send to the decoder
|
||||||
stream->pending.pts = packet->pts;
|
stream->pending->pts = packet->pts;
|
||||||
stream->pending.dts = packet->dts;
|
stream->pending->dts = packet->dts;
|
||||||
stream->pending.flags = packet->flags;
|
stream->pending->flags = packet->flags;
|
||||||
packet = &stream->pending;
|
packet = stream->pending;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_config) {
|
if (is_config) {
|
||||||
// config packet
|
// config packet
|
||||||
bool ok = process_config_packet(stream, packet);
|
bool ok = push_packet_to_sinks(stream, packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -168,10 +149,10 @@ stream_push_packet(struct stream *stream, AVPacket *packet) {
|
|||||||
// data packet
|
// data packet
|
||||||
bool ok = stream_parse(stream, packet);
|
bool ok = stream_parse(stream, packet);
|
||||||
|
|
||||||
if (stream->has_pending) {
|
if (stream->pending) {
|
||||||
// the pending packet must be discarded (consumed or error)
|
// the pending packet must be discarded (consumed or error)
|
||||||
stream->has_pending = false;
|
av_packet_unref(stream->pending);
|
||||||
av_packet_unref(&stream->pending);
|
av_packet_free(&stream->pending);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@@ -181,6 +162,33 @@ stream_push_packet(struct stream *stream, AVPacket *packet) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
stream_close_first_sinks(struct stream *stream, unsigned count) {
|
||||||
|
while (count) {
|
||||||
|
struct sc_packet_sink *sink = stream->sinks[--count];
|
||||||
|
sink->ops->close(sink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
stream_close_sinks(struct stream *stream) {
|
||||||
|
stream_close_first_sinks(stream, stream->sink_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
stream_open_sinks(struct stream *stream, const AVCodec *codec) {
|
||||||
|
for (unsigned i = 0; i < stream->sink_count; ++i) {
|
||||||
|
struct sc_packet_sink *sink = stream->sinks[i];
|
||||||
|
if (!sink->ops->open(sink, codec)) {
|
||||||
|
LOGE("Could not open packet sink %d", i);
|
||||||
|
stream_close_first_sinks(stream, i);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
run_stream(void *data) {
|
run_stream(void *data) {
|
||||||
struct stream *stream = data;
|
struct stream *stream = data;
|
||||||
@@ -197,43 +205,36 @@ run_stream(void *data) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream->decoder && !decoder_open(stream->decoder, codec)) {
|
if (!stream_open_sinks(stream, codec)) {
|
||||||
LOGE("Could not open decoder");
|
LOGE("Could not open stream sinks");
|
||||||
goto finally_free_codec_ctx;
|
goto finally_free_codec_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream->recorder) {
|
|
||||||
if (!recorder_open(stream->recorder, codec)) {
|
|
||||||
LOGE("Could not open recorder");
|
|
||||||
goto finally_close_decoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!recorder_start(stream->recorder)) {
|
|
||||||
LOGE("Could not start recorder");
|
|
||||||
goto finally_close_recorder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stream->parser = av_parser_init(AV_CODEC_ID_H264);
|
stream->parser = av_parser_init(AV_CODEC_ID_H264);
|
||||||
if (!stream->parser) {
|
if (!stream->parser) {
|
||||||
LOGE("Could not initialize parser");
|
LOGE("Could not initialize parser");
|
||||||
goto finally_stop_and_join_recorder;
|
goto finally_close_sinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We must only pass complete frames to av_parser_parse2()!
|
// We must only pass complete frames to av_parser_parse2()!
|
||||||
// It's more complicated, but this allows to reduce the latency by 1 frame!
|
// It's more complicated, but this allows to reduce the latency by 1 frame!
|
||||||
stream->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
|
stream->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
|
||||||
|
|
||||||
|
AVPacket *packet = av_packet_alloc();
|
||||||
|
if (!packet) {
|
||||||
|
LOGE("Could not allocate packet");
|
||||||
|
goto finally_close_parser;
|
||||||
|
}
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
AVPacket packet;
|
bool ok = stream_recv_packet(stream, packet);
|
||||||
bool ok = stream_recv_packet(stream, &packet);
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
// end of stream
|
// end of stream
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = stream_push_packet(stream, &packet);
|
ok = stream_push_packet(stream, packet);
|
||||||
av_packet_unref(&packet);
|
av_packet_unref(packet);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
// cannot process packet (error already logged)
|
// cannot process packet (error already logged)
|
||||||
break;
|
break;
|
||||||
@@ -242,61 +243,58 @@ run_stream(void *data) {
|
|||||||
|
|
||||||
LOGD("End of frames");
|
LOGD("End of frames");
|
||||||
|
|
||||||
if (stream->has_pending) {
|
if (stream->pending) {
|
||||||
av_packet_unref(&stream->pending);
|
av_packet_unref(stream->pending);
|
||||||
|
av_packet_free(&stream->pending);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
av_packet_free(&packet);
|
||||||
|
finally_close_parser:
|
||||||
av_parser_close(stream->parser);
|
av_parser_close(stream->parser);
|
||||||
finally_stop_and_join_recorder:
|
finally_close_sinks:
|
||||||
if (stream->recorder) {
|
stream_close_sinks(stream);
|
||||||
recorder_stop(stream->recorder);
|
|
||||||
LOGI("Finishing recording...");
|
|
||||||
recorder_join(stream->recorder);
|
|
||||||
}
|
|
||||||
finally_close_recorder:
|
|
||||||
if (stream->recorder) {
|
|
||||||
recorder_close(stream->recorder);
|
|
||||||
}
|
|
||||||
finally_close_decoder:
|
|
||||||
if (stream->decoder) {
|
|
||||||
decoder_close(stream->decoder);
|
|
||||||
}
|
|
||||||
finally_free_codec_ctx:
|
finally_free_codec_ctx:
|
||||||
avcodec_free_context(&stream->codec_ctx);
|
avcodec_free_context(&stream->codec_ctx);
|
||||||
end:
|
end:
|
||||||
notify_stopped();
|
stream->cbs->on_eos(stream, stream->cbs_userdata);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
stream_init(struct stream *stream, socket_t socket,
|
stream_init(struct stream *stream, socket_t socket,
|
||||||
struct decoder *decoder, struct recorder *recorder) {
|
const struct stream_callbacks *cbs, void *cbs_userdata) {
|
||||||
stream->socket = socket;
|
stream->socket = socket;
|
||||||
stream->decoder = decoder,
|
stream->pending = NULL;
|
||||||
stream->recorder = recorder;
|
stream->sink_count = 0;
|
||||||
stream->has_pending = false;
|
|
||||||
|
assert(cbs && cbs->on_eos);
|
||||||
|
|
||||||
|
stream->cbs = cbs;
|
||||||
|
stream->cbs_userdata = cbs_userdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
stream_add_sink(struct stream *stream, struct sc_packet_sink *sink) {
|
||||||
|
assert(stream->sink_count < STREAM_MAX_SINKS);
|
||||||
|
assert(sink);
|
||||||
|
assert(sink->ops);
|
||||||
|
stream->sinks[stream->sink_count++] = sink;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
stream_start(struct stream *stream) {
|
stream_start(struct stream *stream) {
|
||||||
LOGD("Starting stream thread");
|
LOGD("Starting stream thread");
|
||||||
|
|
||||||
stream->thread = SDL_CreateThread(run_stream, "stream", stream);
|
bool ok = sc_thread_create(&stream->thread, run_stream, "stream", stream);
|
||||||
if (!stream->thread) {
|
if (!ok) {
|
||||||
LOGC("Could not start stream thread");
|
LOGC("Could not start stream thread");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
stream_stop(struct stream *stream) {
|
|
||||||
if (stream->decoder) {
|
|
||||||
decoder_interrupt(stream->decoder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
stream_join(struct stream *stream) {
|
stream_join(struct stream *stream) {
|
||||||
SDL_WaitThread(stream->thread, NULL);
|
sc_thread_join(&stream->thread, NULL);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,49 @@
|
|||||||
#ifndef STREAM_H
|
#ifndef STREAM_H
|
||||||
#define STREAM_H
|
#define STREAM_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
#include <SDL2/SDL_atomic.h>
|
|
||||||
#include <SDL2/SDL_thread.h>
|
|
||||||
|
|
||||||
#include "config.h"
|
#include "trait/packet_sink.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
|
||||||
struct video_buffer;
|
#define STREAM_MAX_SINKS 2
|
||||||
|
|
||||||
struct stream {
|
struct stream {
|
||||||
socket_t socket;
|
socket_t socket;
|
||||||
SDL_Thread *thread;
|
sc_thread thread;
|
||||||
struct decoder *decoder;
|
|
||||||
struct recorder *recorder;
|
struct sc_packet_sink *sinks[STREAM_MAX_SINKS];
|
||||||
|
unsigned sink_count;
|
||||||
|
|
||||||
AVCodecContext *codec_ctx;
|
AVCodecContext *codec_ctx;
|
||||||
AVCodecParserContext *parser;
|
AVCodecParserContext *parser;
|
||||||
// successive packets may need to be concatenated, until a non-config
|
// successive packets may need to be concatenated, until a non-config
|
||||||
// packet is available
|
// packet is available
|
||||||
bool has_pending;
|
AVPacket *pending;
|
||||||
AVPacket pending;
|
|
||||||
|
const struct stream_callbacks *cbs;
|
||||||
|
void *cbs_userdata;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct stream_callbacks {
|
||||||
|
void (*on_eos)(struct stream *stream, void *userdata);
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
stream_init(struct stream *stream, socket_t socket,
|
stream_init(struct stream *stream, socket_t socket,
|
||||||
struct decoder *decoder, struct recorder *recorder);
|
const struct stream_callbacks *cbs, void *cbs_userdata);
|
||||||
|
|
||||||
|
void
|
||||||
|
stream_add_sink(struct stream *stream, struct sc_packet_sink *sink);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
stream_start(struct stream *stream);
|
stream_start(struct stream *stream);
|
||||||
|
|
||||||
void
|
|
||||||
stream_stop(struct stream *stream);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
stream_join(struct stream *stream);
|
stream_join(struct stream *stream);
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,4 @@
|
|||||||
// for portability (kill, readlink, strdup, strtok_r)
|
#include "util/process.h"
|
||||||
#define _POSIX_C_SOURCE 200809L
|
|
||||||
#define _BSD_SOURCE
|
|
||||||
|
|
||||||
// modern glibc will complain without this
|
|
||||||
#define _DEFAULT_SOURCE
|
|
||||||
|
|
||||||
#ifdef __APPLE__
|
|
||||||
# define _DARWIN_C_SOURCE // for strdup(), strtok_r(), memset_pattern4()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "command.h"
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
@@ -27,7 +14,7 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
cmd_search(const char *file) {
|
search_executable(const char *file) {
|
||||||
char *path = getenv("PATH");
|
char *path = getenv("PATH");
|
||||||
if (!path)
|
if (!path)
|
||||||
return false;
|
return false;
|
||||||
@@ -63,7 +50,7 @@ cmd_search(const char *file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum process_result
|
enum process_result
|
||||||
cmd_execute(const char *const argv[], pid_t *pid) {
|
process_execute(const char *const argv[], pid_t *pid) {
|
||||||
int fd[2];
|
int fd[2];
|
||||||
|
|
||||||
if (pipe(fd) == -1) {
|
if (pipe(fd) == -1) {
|
||||||
@@ -125,29 +112,37 @@ end:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
cmd_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);
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
return kill(pid, SIGTERM) != -1;
|
return kill(pid, SIGKILL) != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
exit_code_t
|
||||||
cmd_simple_wait(pid_t pid, int *exit_code) {
|
process_wait(pid_t pid, bool close) {
|
||||||
int status;
|
|
||||||
int code;
|
int code;
|
||||||
if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) {
|
int options = WEXITED;
|
||||||
|
if (!close) {
|
||||||
|
options |= WNOWAIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
siginfo_t info;
|
||||||
|
int r = waitid(P_PID, pid, &info, options);
|
||||||
|
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 = -1;
|
code = NO_EXIT_CODE;
|
||||||
} else {
|
} else {
|
||||||
code = WEXITSTATUS(status);
|
code = info.si_status;
|
||||||
}
|
}
|
||||||
if (exit_code) {
|
return code;
|
||||||
*exit_code = code;
|
}
|
||||||
}
|
|
||||||
return !code;
|
void
|
||||||
|
process_close(pid_t pid) {
|
||||||
|
process_wait(pid, true); // ignore exit code
|
||||||
}
|
}
|
||||||
|
|
||||||
char *
|
char *
|
||||||
@@ -161,7 +156,7 @@ get_executable_path(void) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
buf[len] = '\0';
|
buf[len] = '\0';
|
||||||
return SDL_strdup(buf);
|
return strdup(buf);
|
||||||
#else
|
#else
|
||||||
// in practice, we only need this feature for portable builds, only used on
|
// in practice, we only need this feature for portable builds, only used on
|
||||||
// Windows, so we don't care implementing it for every platform
|
// Windows, so we don't care implementing it for every platform
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
#include "command.h"
|
#include "util/process.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str_util.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
static int
|
#define CMD_MAX_LEN 8192
|
||||||
|
|
||||||
|
static bool
|
||||||
build_cmd(char *cmd, size_t len, const char *const argv[]) {
|
build_cmd(char *cmd, size_t len, const char *const argv[]) {
|
||||||
// Windows command-line parsing is WTF:
|
// Windows command-line parsing is WTF:
|
||||||
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
|
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
|
||||||
@@ -15,25 +17,26 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) {
|
|||||||
size_t ret = xstrjoin(cmd, argv, ' ', len);
|
size_t ret = xstrjoin(cmd, argv, ' ', len);
|
||||||
if (ret >= len) {
|
if (ret >= len) {
|
||||||
LOGE("Command too long (%" PRIsizet " chars)", len - 1);
|
LOGE("Command too long (%" PRIsizet " chars)", len - 1);
|
||||||
return -1;
|
return false;
|
||||||
}
|
}
|
||||||
return 0;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum process_result
|
enum process_result
|
||||||
cmd_execute(const char *const argv[], HANDLE *handle) {
|
process_execute(const char *const argv[], HANDLE *handle) {
|
||||||
STARTUPINFOW si;
|
STARTUPINFOW si;
|
||||||
PROCESS_INFORMATION pi;
|
PROCESS_INFORMATION pi;
|
||||||
memset(&si, 0, sizeof(si));
|
memset(&si, 0, sizeof(si));
|
||||||
si.cb = sizeof(si);
|
si.cb = sizeof(si);
|
||||||
|
|
||||||
char cmd[256];
|
char *cmd = malloc(CMD_MAX_LEN);
|
||||||
if (build_cmd(cmd, sizeof(cmd), argv)) {
|
if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) {
|
||||||
*handle = NULL;
|
*handle = NULL;
|
||||||
return PROCESS_ERROR_GENERIC;
|
return PROCESS_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
wchar_t *wide = utf8_to_wide_char(cmd);
|
wchar_t *wide = utf8_to_wide_char(cmd);
|
||||||
|
free(cmd);
|
||||||
if (!wide) {
|
if (!wide) {
|
||||||
LOGC("Could not allocate wide char string");
|
LOGC("Could not allocate wide char string");
|
||||||
return PROCESS_ERROR_GENERIC;
|
return PROCESS_ERROR_GENERIC;
|
||||||
@@ -41,7 +44,7 @@ cmd_execute(const char *const argv[], HANDLE *handle) {
|
|||||||
|
|
||||||
if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, 0, NULL, NULL, &si,
|
if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, 0, NULL, NULL, &si,
|
||||||
&pi)) {
|
&pi)) {
|
||||||
SDL_free(wide);
|
free(wide);
|
||||||
*handle = NULL;
|
*handle = NULL;
|
||||||
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
|
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
|
||||||
return PROCESS_ERROR_MISSING_BINARY;
|
return PROCESS_ERROR_MISSING_BINARY;
|
||||||
@@ -49,29 +52,35 @@ cmd_execute(const char *const argv[], HANDLE *handle) {
|
|||||||
return PROCESS_ERROR_GENERIC;
|
return PROCESS_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_free(wide);
|
free(wide);
|
||||||
*handle = pi.hProcess;
|
*handle = pi.hProcess;
|
||||||
return PROCESS_SUCCESS;
|
return PROCESS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
cmd_terminate(HANDLE handle) {
|
process_terminate(HANDLE handle) {
|
||||||
return TerminateProcess(handle, 1);
|
return TerminateProcess(handle, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
exit_code_t
|
||||||
cmd_simple_wait(HANDLE handle, DWORD *exit_code) {
|
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 = -1; // max value, it's unsigned
|
code = NO_EXIT_CODE; // max value, it's unsigned
|
||||||
}
|
|
||||||
if (exit_code) {
|
|
||||||
*exit_code = code;
|
|
||||||
}
|
}
|
||||||
|
if (close) {
|
||||||
CloseHandle(handle);
|
CloseHandle(handle);
|
||||||
return !code;
|
}
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
process_close(HANDLE handle) {
|
||||||
|
bool closed = CloseHandle(handle);
|
||||||
|
assert(closed);
|
||||||
|
(void) closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *
|
char *
|
||||||
@@ -99,7 +108,7 @@ is_regular_file(const char *path) {
|
|||||||
|
|
||||||
struct _stat path_stat;
|
struct _stat path_stat;
|
||||||
int r = _wstat(wide_path, &path_stat);
|
int r = _wstat(wide_path, &path_stat);
|
||||||
SDL_free(wide_path);
|
free(wide_path);
|
||||||
|
|
||||||
if (r) {
|
if (r) {
|
||||||
perror("stat");
|
perror("stat");
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
struct index {
|
struct index {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#ifndef TINYXPM_H
|
#ifndef TINYXPM_H
|
||||||
#define TINYXPM_H
|
#define TINYXPM_H
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include "common.h"
|
||||||
|
|
||||||
#include "config.h"
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
SDL_Surface *
|
SDL_Surface *
|
||||||
read_xpm(char *xpm[]);
|
read_xpm(char *xpm[]);
|
||||||
|
|||||||
26
app/src/trait/frame_sink.h
Normal file
26
app/src/trait/frame_sink.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#ifndef SC_FRAME_SINK
|
||||||
|
#define SC_FRAME_SINK
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef struct AVFrame AVFrame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frame sink trait.
|
||||||
|
*
|
||||||
|
* Component able to receive AVFrames should implement this trait.
|
||||||
|
*/
|
||||||
|
struct sc_frame_sink {
|
||||||
|
const struct sc_frame_sink_ops *ops;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_frame_sink_ops {
|
||||||
|
bool (*open)(struct sc_frame_sink *sink);
|
||||||
|
void (*close)(struct sc_frame_sink *sink);
|
||||||
|
bool (*push)(struct sc_frame_sink *sink, const AVFrame *frame);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
27
app/src/trait/packet_sink.h
Normal file
27
app/src/trait/packet_sink.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#ifndef SC_PACKET_SINK
|
||||||
|
#define SC_PACKET_SINK
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef struct AVCodec AVCodec;
|
||||||
|
typedef struct AVPacket AVPacket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Packet sink trait.
|
||||||
|
*
|
||||||
|
* Component able to receive AVPackets should implement this trait.
|
||||||
|
*/
|
||||||
|
struct sc_packet_sink {
|
||||||
|
const struct sc_packet_sink_ops *ops;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_packet_sink_ops {
|
||||||
|
bool (*open)(struct sc_packet_sink *sink, const AVCodec *codec);
|
||||||
|
void (*close)(struct sc_packet_sink *sink);
|
||||||
|
bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
#ifndef BUFFER_UTIL_H
|
#ifndef BUFFER_UTIL_H
|
||||||
#define BUFFER_UTIL_H
|
#define BUFFER_UTIL_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
buffer_write16be(uint8_t *buf, uint16_t value) {
|
buffer_write16be(uint8_t *buf, uint16_t value) {
|
||||||
buf[0] = value >> 8;
|
buf[0] = value >> 8;
|
||||||
@@ -33,7 +33,7 @@ buffer_read16be(const uint8_t *buf) {
|
|||||||
|
|
||||||
static inline uint32_t
|
static inline uint32_t
|
||||||
buffer_read32be(const uint8_t *buf) {
|
buffer_read32be(const uint8_t *buf) {
|
||||||
return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
|
return ((uint32_t) buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline uint64_t
|
static inline uint64_t
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
#ifndef CBUF_H
|
#ifndef CBUF_H
|
||||||
#define CBUF_H
|
#define CBUF_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
// To define a circular buffer type of 20 ints:
|
// To define a circular buffer type of 20 ints:
|
||||||
// struct cbuf_int CBUF(int, 20);
|
// struct cbuf_int CBUF(int, 20);
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
#ifndef LOCK_H
|
|
||||||
#define LOCK_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "log.h"
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
mutex_lock(SDL_mutex *mutex) {
|
|
||||||
int r = SDL_LockMutex(mutex);
|
|
||||||
#ifndef NDEBUG
|
|
||||||
if (r) {
|
|
||||||
LOGC("Could not lock mutex: %s", SDL_GetError());
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
(void) r;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
mutex_unlock(SDL_mutex *mutex) {
|
|
||||||
int r = SDL_UnlockMutex(mutex);
|
|
||||||
#ifndef NDEBUG
|
|
||||||
if (r) {
|
|
||||||
LOGC("Could not unlock mutex: %s", SDL_GetError());
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
(void) r;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
|
|
||||||
int r = SDL_CondWait(cond, mutex);
|
|
||||||
#ifndef NDEBUG
|
|
||||||
if (r) {
|
|
||||||
LOGC("Could not wait on condition: %s", SDL_GetError());
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
(void) r;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int
|
|
||||||
cond_wait_timeout(SDL_cond *cond, SDL_mutex *mutex, uint32_t ms) {
|
|
||||||
int r = SDL_CondWaitTimeout(cond, mutex, ms);
|
|
||||||
#ifndef NDEBUG
|
|
||||||
if (r < 0) {
|
|
||||||
LOGC("Could not wait on condition with timeout: %s", SDL_GetError());
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
cond_signal(SDL_cond *cond) {
|
|
||||||
int r = SDL_CondSignal(cond);
|
|
||||||
#ifndef NDEBUG
|
|
||||||
if (r) {
|
|
||||||
LOGC("Could not signal a condition: %s", SDL_GetError());
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
(void) r;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
53
app/src/util/log.c
Normal file
53
app/src/util/log.c
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
static SDL_LogPriority
|
||||||
|
log_level_sc_to_sdl(enum sc_log_level level) {
|
||||||
|
switch (level) {
|
||||||
|
case SC_LOG_LEVEL_VERBOSE:
|
||||||
|
return SDL_LOG_PRIORITY_VERBOSE;
|
||||||
|
case SC_LOG_LEVEL_DEBUG:
|
||||||
|
return SDL_LOG_PRIORITY_DEBUG;
|
||||||
|
case SC_LOG_LEVEL_INFO:
|
||||||
|
return SDL_LOG_PRIORITY_INFO;
|
||||||
|
case SC_LOG_LEVEL_WARN:
|
||||||
|
return SDL_LOG_PRIORITY_WARN;
|
||||||
|
case SC_LOG_LEVEL_ERROR:
|
||||||
|
return SDL_LOG_PRIORITY_ERROR;
|
||||||
|
default:
|
||||||
|
assert(!"unexpected log level");
|
||||||
|
return SDL_LOG_PRIORITY_INFO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum sc_log_level
|
||||||
|
log_level_sdl_to_sc(SDL_LogPriority priority) {
|
||||||
|
switch (priority) {
|
||||||
|
case SDL_LOG_PRIORITY_VERBOSE:
|
||||||
|
return SC_LOG_LEVEL_VERBOSE;
|
||||||
|
case SDL_LOG_PRIORITY_DEBUG:
|
||||||
|
return SC_LOG_LEVEL_DEBUG;
|
||||||
|
case SDL_LOG_PRIORITY_INFO:
|
||||||
|
return SC_LOG_LEVEL_INFO;
|
||||||
|
case SDL_LOG_PRIORITY_WARN:
|
||||||
|
return SC_LOG_LEVEL_WARN;
|
||||||
|
case SDL_LOG_PRIORITY_ERROR:
|
||||||
|
return SC_LOG_LEVEL_ERROR;
|
||||||
|
default:
|
||||||
|
assert(!"unexpected log level");
|
||||||
|
return SC_LOG_LEVEL_INFO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_set_log_level(enum sc_log_level level) {
|
||||||
|
SDL_LogPriority sdl_log = log_level_sc_to_sdl(level);
|
||||||
|
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, sdl_log);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum sc_log_level
|
||||||
|
sc_get_log_level(void) {
|
||||||
|
SDL_LogPriority sdl_log = SDL_LogGetPriority(SDL_LOG_CATEGORY_APPLICATION);
|
||||||
|
return log_level_sdl_to_sc(sdl_log);
|
||||||
|
}
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
#ifndef LOG_H
|
#ifndef SC_LOG_H
|
||||||
#define LOG_H
|
#define SC_LOG_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <SDL2/SDL_log.h>
|
#include <SDL2/SDL_log.h>
|
||||||
|
|
||||||
|
#include "scrcpy.h"
|
||||||
|
|
||||||
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
||||||
#define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
#define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
||||||
#define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
#define LOGI(...) SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
||||||
@@ -10,4 +14,10 @@
|
|||||||
#define LOGE(...) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
#define LOGE(...) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
||||||
#define LOGC(...) SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
#define LOGC(...) SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_set_log_level(enum sc_log_level level);
|
||||||
|
|
||||||
|
enum sc_log_level
|
||||||
|
sc_get_log_level(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <SDL2/SDL_platform.h>
|
#include <SDL2/SDL_platform.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
#ifdef __WINDOWS__
|
#ifdef __WINDOWS__
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#ifndef NET_H
|
#ifndef NET_H
|
||||||
#define NET_H
|
#define NET_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <SDL2/SDL_platform.h>
|
#include <SDL2/SDL_platform.h>
|
||||||
@@ -17,8 +19,6 @@
|
|||||||
typedef int socket_t;
|
typedef int socket_t;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
net_init(void);
|
net_init(void);
|
||||||
|
|
||||||
|
|||||||
21
app/src/util/process.c
Normal file
21
app/src/util/process.c
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#include "process.h"
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
bool
|
||||||
|
process_check_success(process_t proc, const char *name, bool close) {
|
||||||
|
if (proc == PROCESS_NONE) {
|
||||||
|
LOGE("Could not execute \"%s\"", name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
exit_code_t exit_code = process_wait(proc, close);
|
||||||
|
if (exit_code) {
|
||||||
|
if (exit_code != NO_EXIT_CODE) {
|
||||||
|
LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code);
|
||||||
|
} else {
|
||||||
|
LOGE("\"%s\" exited unexpectedly", name);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
#ifndef COMMAND_H
|
#ifndef SC_PROCESS_H
|
||||||
#define COMMAND_H
|
#define SC_PROCESS_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <inttypes.h>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
@@ -31,58 +32,45 @@
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
enum process_result {
|
enum process_result {
|
||||||
PROCESS_SUCCESS,
|
PROCESS_SUCCESS,
|
||||||
PROCESS_ERROR_GENERIC,
|
PROCESS_ERROR_GENERIC,
|
||||||
PROCESS_ERROR_MISSING_BINARY,
|
PROCESS_ERROR_MISSING_BINARY,
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifndef __WINDOWS__
|
// execute the command and write the result to the output parameter "process"
|
||||||
bool
|
|
||||||
cmd_search(const char *file);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
enum process_result
|
enum process_result
|
||||||
cmd_execute(const char *const argv[], process_t *process);
|
process_execute(const char *const argv[], process_t *process);
|
||||||
|
|
||||||
|
// kill the process
|
||||||
bool
|
bool
|
||||||
cmd_terminate(process_t pid);
|
process_terminate(process_t pid);
|
||||||
|
|
||||||
bool
|
// wait and close the process (like waitpid())
|
||||||
cmd_simple_wait(process_t pid, exit_code_t *exit_code);
|
// the "close" flag indicates if the process must be "closed" (reaped)
|
||||||
|
// (passing false is equivalent to enable WNOWAIT in waitid())
|
||||||
|
exit_code_t
|
||||||
|
process_wait(process_t pid, bool close);
|
||||||
|
|
||||||
process_t
|
// close the process
|
||||||
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
|
//
|
||||||
|
// Semantically, process_wait(close) = process_wait(noclose) + process_close
|
||||||
process_t
|
void
|
||||||
adb_forward(const char *serial, uint16_t local_port,
|
process_close(process_t pid);
|
||||||
const char *device_socket_name);
|
|
||||||
|
|
||||||
process_t
|
|
||||||
adb_forward_remove(const char *serial, uint16_t local_port);
|
|
||||||
|
|
||||||
process_t
|
|
||||||
adb_reverse(const char *serial, const char *device_socket_name,
|
|
||||||
uint16_t local_port);
|
|
||||||
|
|
||||||
process_t
|
|
||||||
adb_reverse_remove(const char *serial, const char *device_socket_name);
|
|
||||||
|
|
||||||
process_t
|
|
||||||
adb_push(const char *serial, const char *local, const char *remote);
|
|
||||||
|
|
||||||
process_t
|
|
||||||
adb_install(const char *serial, const char *local);
|
|
||||||
|
|
||||||
// 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
|
||||||
process_check_success(process_t proc, const char *name);
|
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)
|
// return the absolute path of the executable (the scrcpy binary)
|
||||||
// may be NULL on error; to be freed by SDL_free
|
// may be NULL on error; to be freed by free()
|
||||||
char *
|
char *
|
||||||
get_executable_path(void);
|
get_executable_path(void);
|
||||||
|
|
||||||
@@ -2,12 +2,12 @@
|
|||||||
#ifndef QUEUE_H
|
#ifndef QUEUE_H
|
||||||
#define QUEUE_H
|
#define QUEUE_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
// To define a queue type of "struct foo":
|
// To define a queue type of "struct foo":
|
||||||
// struct queue_foo QUEUE(struct foo);
|
// struct queue_foo QUEUE(struct foo);
|
||||||
#define QUEUE(TYPE) { \
|
#define QUEUE(TYPE) { \
|
||||||
|
|||||||
@@ -10,10 +10,6 @@
|
|||||||
# include <tchar.h>
|
# include <tchar.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <SDL2/SDL_stdinc.h>
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
xstrncpy(char *dest, const char *src, size_t n) {
|
xstrncpy(char *dest, const char *src, size_t n) {
|
||||||
size_t i;
|
size_t i;
|
||||||
@@ -51,7 +47,7 @@ truncated:
|
|||||||
char *
|
char *
|
||||||
strquote(const char *src) {
|
strquote(const char *src) {
|
||||||
size_t len = strlen(src);
|
size_t len = strlen(src);
|
||||||
char *quoted = SDL_malloc(len + 3);
|
char *quoted = malloc(len + 3);
|
||||||
if (!quoted) {
|
if (!quoted) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -144,6 +140,24 @@ parse_integer_with_suffix(const char *s, long *out) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
strlist_contains(const char *list, char sep, const char *s) {
|
||||||
|
char *p;
|
||||||
|
do {
|
||||||
|
p = strchr(list, sep);
|
||||||
|
|
||||||
|
size_t token_len = p ? (size_t) (p - list) : strlen(list);
|
||||||
|
if (!strncmp(list, s, token_len)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p) {
|
||||||
|
list = p + 1;
|
||||||
|
}
|
||||||
|
} while (p);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
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);
|
||||||
@@ -169,7 +183,7 @@ utf8_to_wide_char(const char *utf8) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
wchar_t *wide = SDL_malloc(len * sizeof(wchar_t));
|
wchar_t *wide = malloc(len * sizeof(wchar_t));
|
||||||
if (!wide) {
|
if (!wide) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -185,7 +199,7 @@ utf8_from_wide_char(const wchar_t *ws) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *utf8 = SDL_malloc(len);
|
char *utf8 = malloc(len);
|
||||||
if (!utf8) {
|
if (!utf8) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#ifndef STRUTIL_H
|
#ifndef STRUTIL_H
|
||||||
#define STRUTIL_H
|
#define STRUTIL_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
// like strncpy, except:
|
// like strncpy, except:
|
||||||
// - it copies at most n-1 chars
|
// - it copies at most n-1 chars
|
||||||
// - the dest string is nul-terminated
|
// - the dest string is nul-terminated
|
||||||
@@ -16,7 +16,7 @@ size_t
|
|||||||
xstrncpy(char *dest, const char *src, size_t n);
|
xstrncpy(char *dest, const char *src, size_t n);
|
||||||
|
|
||||||
// join tokens by sep into dst
|
// join tokens by sep into dst
|
||||||
// returns the number of chars actually written (max n-1) if no trucation
|
// returns the number of chars actually written (max n-1) if no truncation
|
||||||
// occurred, or n if truncated
|
// occurred, or n if truncated
|
||||||
size_t
|
size_t
|
||||||
xstrjoin(char *dst, const char *const tokens[], char sep, size_t n);
|
xstrjoin(char *dst, const char *const tokens[], char sep, size_t n);
|
||||||
@@ -43,6 +43,11 @@ parse_integers(const char *s, const char sep, size_t max_items, long *out);
|
|||||||
bool
|
bool
|
||||||
parse_integer_with_suffix(const char *s, long *out);
|
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
|
// return the index to truncate a UTF-8 string at a valid position
|
||||||
size_t
|
size_t
|
||||||
utf8_truncation_index(const char *utf8, size_t max_len);
|
utf8_truncation_index(const char *utf8, size_t max_len);
|
||||||
|
|||||||
160
app/src/util/thread.c
Normal file
160
app/src/util/thread.c
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
#include "thread.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <SDL2/SDL_thread.h>
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
|
||||||
|
void *userdata) {
|
||||||
|
SDL_Thread *sdl_thread = SDL_CreateThread(fn, name, userdata);
|
||||||
|
if (!sdl_thread) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread->thread = sdl_thread;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_thread_join(sc_thread *thread, int *status) {
|
||||||
|
SDL_WaitThread(thread->thread, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_mutex_init(sc_mutex *mutex) {
|
||||||
|
SDL_mutex *sdl_mutex = SDL_CreateMutex();
|
||||||
|
if (!sdl_mutex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex->mutex = sdl_mutex;
|
||||||
|
#ifndef NDEBUG
|
||||||
|
mutex->locker = 0;
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_mutex_destroy(sc_mutex *mutex) {
|
||||||
|
SDL_DestroyMutex(mutex->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_mutex_lock(sc_mutex *mutex) {
|
||||||
|
// SDL mutexes are recursive, but we don't want to use recursive mutexes
|
||||||
|
assert(!sc_mutex_held(mutex));
|
||||||
|
int r = SDL_LockMutex(mutex->mutex);
|
||||||
|
#ifndef NDEBUG
|
||||||
|
if (r) {
|
||||||
|
LOGC("Could not lock mutex: %s", SDL_GetError());
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex->locker = sc_thread_get_id();
|
||||||
|
#else
|
||||||
|
(void) r;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_mutex_unlock(sc_mutex *mutex) {
|
||||||
|
#ifndef NDEBUG
|
||||||
|
assert(sc_mutex_held(mutex));
|
||||||
|
mutex->locker = 0;
|
||||||
|
#endif
|
||||||
|
int r = SDL_UnlockMutex(mutex->mutex);
|
||||||
|
#ifndef NDEBUG
|
||||||
|
if (r) {
|
||||||
|
LOGC("Could not lock mutex: %s", SDL_GetError());
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void) r;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_thread_id
|
||||||
|
sc_thread_get_id(void) {
|
||||||
|
return SDL_ThreadID();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
bool
|
||||||
|
sc_mutex_held(struct sc_mutex *mutex) {
|
||||||
|
return mutex->locker == sc_thread_get_id();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_cond_init(sc_cond *cond) {
|
||||||
|
SDL_cond *sdl_cond = SDL_CreateCond();
|
||||||
|
if (!sdl_cond) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cond->cond = sdl_cond;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_cond_destroy(sc_cond *cond) {
|
||||||
|
SDL_DestroyCond(cond->cond);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_cond_wait(sc_cond *cond, sc_mutex *mutex) {
|
||||||
|
int r = SDL_CondWait(cond->cond, mutex->mutex);
|
||||||
|
#ifndef NDEBUG
|
||||||
|
if (r) {
|
||||||
|
LOGC("Could not wait on condition: %s", SDL_GetError());
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex->locker = sc_thread_get_id();
|
||||||
|
#else
|
||||||
|
(void) r;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms) {
|
||||||
|
int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms);
|
||||||
|
#ifndef NDEBUG
|
||||||
|
if (r < 0) {
|
||||||
|
LOGC("Could not wait on condition with timeout: %s", SDL_GetError());
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex->locker = sc_thread_get_id();
|
||||||
|
#endif
|
||||||
|
assert(r == 0 || r == SDL_MUTEX_TIMEDOUT);
|
||||||
|
return r == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_cond_signal(sc_cond *cond) {
|
||||||
|
int r = SDL_CondSignal(cond->cond);
|
||||||
|
#ifndef NDEBUG
|
||||||
|
if (r) {
|
||||||
|
LOGC("Could not signal a condition: %s", SDL_GetError());
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void) r;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_cond_broadcast(sc_cond *cond) {
|
||||||
|
int r = SDL_CondBroadcast(cond->cond);
|
||||||
|
#ifndef NDEBUG
|
||||||
|
if (r) {
|
||||||
|
LOGC("Could not broadcast a condition: %s", SDL_GetError());
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void) r;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
81
app/src/util/thread.h
Normal file
81
app/src/util/thread.h
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
#ifndef SC_THREAD_H
|
||||||
|
#define SC_THREAD_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* Forward declarations */
|
||||||
|
typedef struct SDL_Thread SDL_Thread;
|
||||||
|
typedef struct SDL_mutex SDL_mutex;
|
||||||
|
typedef struct SDL_cond SDL_cond;
|
||||||
|
|
||||||
|
typedef int sc_thread_fn(void *);
|
||||||
|
typedef unsigned int sc_thread_id;
|
||||||
|
|
||||||
|
typedef struct sc_thread {
|
||||||
|
SDL_Thread *thread;
|
||||||
|
} sc_thread;
|
||||||
|
|
||||||
|
typedef struct sc_mutex {
|
||||||
|
SDL_mutex *mutex;
|
||||||
|
#ifndef NDEBUG
|
||||||
|
sc_thread_id locker;
|
||||||
|
#endif
|
||||||
|
} sc_mutex;
|
||||||
|
|
||||||
|
typedef struct sc_cond {
|
||||||
|
SDL_cond *cond;
|
||||||
|
} sc_cond;
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
|
||||||
|
void *userdata);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_thread_join(sc_thread *thread, int *status);
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_mutex_init(sc_mutex *mutex);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_mutex_destroy(sc_mutex *mutex);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_mutex_lock(sc_mutex *mutex);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_mutex_unlock(sc_mutex *mutex);
|
||||||
|
|
||||||
|
sc_thread_id
|
||||||
|
sc_thread_get_id(void);
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
bool
|
||||||
|
sc_mutex_held(struct sc_mutex *mutex);
|
||||||
|
# define sc_mutex_assert(mutex) assert(sc_mutex_held(mutex))
|
||||||
|
#else
|
||||||
|
# define sc_mutex_assert(mutex)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_cond_init(sc_cond *cond);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_cond_destroy(sc_cond *cond);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_cond_wait(sc_cond *cond, sc_mutex *mutex);
|
||||||
|
|
||||||
|
// return true on signaled, false on timeout
|
||||||
|
bool
|
||||||
|
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_cond_signal(sc_cond *cond);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_cond_broadcast(sc_cond *cond);
|
||||||
|
|
||||||
|
#endif
|
||||||
356
app/src/v4l2_sink.c
Normal file
356
app/src/v4l2_sink.c
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
#include "v4l2_sink.h"
|
||||||
|
|
||||||
|
#include "util/log.h"
|
||||||
|
#include "util/str_util.h"
|
||||||
|
|
||||||
|
/** Downcast frame_sink to sc_v4l2_sink */
|
||||||
|
#define DOWNCAST(SINK) container_of(SINK, struct sc_v4l2_sink, frame_sink)
|
||||||
|
|
||||||
|
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
|
||||||
|
|
||||||
|
static const AVOutputFormat *
|
||||||
|
find_muxer(const char *name) {
|
||||||
|
#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
|
||||||
|
void *opaque = NULL;
|
||||||
|
#endif
|
||||||
|
const AVOutputFormat *oformat = NULL;
|
||||||
|
do {
|
||||||
|
#ifdef SCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
|
||||||
|
oformat = av_muxer_iterate(&opaque);
|
||||||
|
#else
|
||||||
|
oformat = av_oformat_next(oformat);
|
||||||
|
#endif
|
||||||
|
// until null or containing the requested name
|
||||||
|
} while (oformat && !strlist_contains(oformat->name, ',', name));
|
||||||
|
return oformat;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
write_header(struct sc_v4l2_sink *vs, const AVPacket *packet) {
|
||||||
|
AVStream *ostream = vs->format_ctx->streams[0];
|
||||||
|
|
||||||
|
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
|
||||||
|
if (!extradata) {
|
||||||
|
LOGC("Could not allocate extradata");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy the first packet to the extra data
|
||||||
|
memcpy(extradata, packet->data, packet->size);
|
||||||
|
|
||||||
|
ostream->codecpar->extradata = extradata;
|
||||||
|
ostream->codecpar->extradata_size = packet->size;
|
||||||
|
|
||||||
|
int ret = avformat_write_header(vs->format_ctx, NULL);
|
||||||
|
if (ret < 0) {
|
||||||
|
LOGE("Failed to write header to %s", vs->device_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rescale_packet(struct sc_v4l2_sink *vs, AVPacket *packet) {
|
||||||
|
AVStream *ostream = vs->format_ctx->streams[0];
|
||||||
|
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
write_packet(struct sc_v4l2_sink *vs, AVPacket *packet) {
|
||||||
|
if (!vs->header_written) {
|
||||||
|
bool ok = write_header(vs, packet);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vs->header_written = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
rescale_packet(vs, packet);
|
||||||
|
|
||||||
|
bool ok = av_write_frame(vs->format_ctx, packet) >= 0;
|
||||||
|
|
||||||
|
// Failing to write the last frame is not very serious, no future frame may
|
||||||
|
// depend on it, so the resulting file will still be valid
|
||||||
|
(void) ok;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
encode_and_write_frame(struct sc_v4l2_sink *vs, const AVFrame *frame) {
|
||||||
|
int ret = avcodec_send_frame(vs->encoder_ctx, frame);
|
||||||
|
if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
||||||
|
LOGE("Could not send v4l2 video frame: %d", ret);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVPacket *packet = vs->packet;
|
||||||
|
ret = avcodec_receive_packet(vs->encoder_ctx, packet);
|
||||||
|
if (ret == 0) {
|
||||||
|
// A packet was received
|
||||||
|
|
||||||
|
bool ok = write_packet(vs, packet);
|
||||||
|
av_packet_unref(packet);
|
||||||
|
if (!ok) {
|
||||||
|
LOGW("Could not send packet to v4l2 sink");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (ret != AVERROR(EAGAIN)) {
|
||||||
|
LOGE("Could not receive v4l2 video packet: %d", ret);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_v4l2_sink(void *data) {
|
||||||
|
struct sc_v4l2_sink *vs = data;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
sc_mutex_lock(&vs->mutex);
|
||||||
|
|
||||||
|
while (!vs->stopped && vs->vb.pending_frame_consumed) {
|
||||||
|
sc_cond_wait(&vs->cond, &vs->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vs->stopped) {
|
||||||
|
sc_mutex_unlock(&vs->mutex);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_mutex_unlock(&vs->mutex);
|
||||||
|
|
||||||
|
video_buffer_consume(&vs->vb, vs->frame);
|
||||||
|
bool ok = encode_and_write_frame(vs, vs->frame);
|
||||||
|
av_frame_unref(vs->frame);
|
||||||
|
if (!ok) {
|
||||||
|
LOGE("Could not send frame to v4l2 sink");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGD("V4l2 thread ended");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
|
||||||
|
bool ok = video_buffer_init(&vs->vb);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = sc_mutex_init(&vs->mutex);
|
||||||
|
if (!ok) {
|
||||||
|
LOGC("Could not create mutex");
|
||||||
|
goto error_video_buffer_destroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = sc_cond_init(&vs->cond);
|
||||||
|
if (!ok) {
|
||||||
|
LOGC("Could not create cond");
|
||||||
|
goto error_mutex_destroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME
|
||||||
|
const AVOutputFormat *format = find_muxer("video4linux2,v4l2");
|
||||||
|
if (!format) {
|
||||||
|
LOGE("Could not find v4l2 muxer");
|
||||||
|
goto error_cond_destroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVCodec *encoder = avcodec_find_encoder(AV_CODEC_ID_RAWVIDEO);
|
||||||
|
if (!encoder) {
|
||||||
|
LOGE("Raw video encoder not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
vs->format_ctx = avformat_alloc_context();
|
||||||
|
if (!vs->format_ctx) {
|
||||||
|
LOGE("Could not allocate v4l2 output context");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
|
||||||
|
// returns (on purpose) a pointer-to-const, but AVFormatContext.oformat
|
||||||
|
// still expects a pointer-to-non-const (it has not be updated accordingly)
|
||||||
|
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
|
||||||
|
vs->format_ctx->oformat = (AVOutputFormat *) format;
|
||||||
|
#ifdef SCRCPY_LAVF_HAS_AVFORMATCONTEXT_URL
|
||||||
|
vs->format_ctx->url = strdup(vs->device_name);
|
||||||
|
if (!vs->format_ctx->url) {
|
||||||
|
LOGE("Could not strdup v4l2 device name");
|
||||||
|
goto error_avformat_free_context;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
strncpy(vs->format_ctx->filename, vs->device_name,
|
||||||
|
sizeof(vs->format_ctx->filename));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
AVStream *ostream = avformat_new_stream(vs->format_ctx, encoder);
|
||||||
|
if (!ostream) {
|
||||||
|
LOGE("Could not allocate new v4l2 stream");
|
||||||
|
goto error_avformat_free_context;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||||
|
ostream->codecpar->codec_id = encoder->id;
|
||||||
|
ostream->codecpar->format = AV_PIX_FMT_YUV420P;
|
||||||
|
ostream->codecpar->width = vs->frame_size.width;
|
||||||
|
ostream->codecpar->height = vs->frame_size.height;
|
||||||
|
|
||||||
|
int ret = avio_open(&vs->format_ctx->pb, vs->device_name, AVIO_FLAG_WRITE);
|
||||||
|
if (ret < 0) {
|
||||||
|
LOGE("Failed to open output device: %s", vs->device_name);
|
||||||
|
// ostream will be cleaned up during context cleaning
|
||||||
|
goto error_avformat_free_context;
|
||||||
|
}
|
||||||
|
|
||||||
|
vs->encoder_ctx = avcodec_alloc_context3(encoder);
|
||||||
|
if (!vs->encoder_ctx) {
|
||||||
|
LOGC("Could not allocate codec context for v4l2");
|
||||||
|
goto error_avio_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
vs->encoder_ctx->width = vs->frame_size.width;
|
||||||
|
vs->encoder_ctx->height = vs->frame_size.height;
|
||||||
|
vs->encoder_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||||
|
vs->encoder_ctx->time_base.num = 1;
|
||||||
|
vs->encoder_ctx->time_base.den = 1;
|
||||||
|
|
||||||
|
if (avcodec_open2(vs->encoder_ctx, encoder, NULL) < 0) {
|
||||||
|
LOGE("Could not open codec for v4l2");
|
||||||
|
goto error_avcodec_free_context;
|
||||||
|
}
|
||||||
|
|
||||||
|
vs->frame = av_frame_alloc();
|
||||||
|
if (!vs->frame) {
|
||||||
|
LOGE("Could not create v4l2 frame");
|
||||||
|
goto error_avcodec_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
vs->packet = av_packet_alloc();
|
||||||
|
if (!vs->packet) {
|
||||||
|
LOGE("Could not allocate packet");
|
||||||
|
goto error_av_frame_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGD("Starting v4l2 thread");
|
||||||
|
ok = sc_thread_create(&vs->thread, run_v4l2_sink, "v4l2", vs);
|
||||||
|
if (!ok) {
|
||||||
|
LOGC("Could not start v4l2 thread");
|
||||||
|
goto error_av_packet_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
vs->header_written = false;
|
||||||
|
vs->stopped = false;
|
||||||
|
|
||||||
|
LOGI("v4l2 sink started to device: %s", vs->device_name);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
error_av_packet_free:
|
||||||
|
av_packet_free(&vs->packet);
|
||||||
|
error_av_frame_free:
|
||||||
|
av_frame_free(&vs->frame);
|
||||||
|
error_avcodec_close:
|
||||||
|
avcodec_close(vs->encoder_ctx);
|
||||||
|
error_avcodec_free_context:
|
||||||
|
avcodec_free_context(&vs->encoder_ctx);
|
||||||
|
error_avio_close:
|
||||||
|
avio_close(vs->format_ctx->pb);
|
||||||
|
error_avformat_free_context:
|
||||||
|
avformat_free_context(vs->format_ctx);
|
||||||
|
error_cond_destroy:
|
||||||
|
sc_cond_destroy(&vs->cond);
|
||||||
|
error_mutex_destroy:
|
||||||
|
sc_mutex_destroy(&vs->mutex);
|
||||||
|
error_video_buffer_destroy:
|
||||||
|
video_buffer_destroy(&vs->vb);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
|
||||||
|
sc_mutex_lock(&vs->mutex);
|
||||||
|
vs->stopped = true;
|
||||||
|
sc_cond_signal(&vs->cond);
|
||||||
|
sc_mutex_unlock(&vs->mutex);
|
||||||
|
|
||||||
|
sc_thread_join(&vs->thread, NULL);
|
||||||
|
|
||||||
|
av_packet_free(&vs->packet);
|
||||||
|
av_frame_free(&vs->frame);
|
||||||
|
avcodec_close(vs->encoder_ctx);
|
||||||
|
avcodec_free_context(&vs->encoder_ctx);
|
||||||
|
avio_close(vs->format_ctx->pb);
|
||||||
|
avformat_free_context(vs->format_ctx);
|
||||||
|
sc_cond_destroy(&vs->cond);
|
||||||
|
sc_mutex_destroy(&vs->mutex);
|
||||||
|
video_buffer_destroy(&vs->vb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_v4l2_sink_push(struct sc_v4l2_sink *vs, const AVFrame *frame) {
|
||||||
|
bool ok = video_buffer_push(&vs->vb, frame, NULL);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// signal possible change of vs->vb.pending_frame_consumed
|
||||||
|
sc_cond_signal(&vs->cond);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_v4l2_frame_sink_open(struct sc_frame_sink *sink) {
|
||||||
|
struct sc_v4l2_sink *vs = DOWNCAST(sink);
|
||||||
|
return sc_v4l2_sink_open(vs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_v4l2_frame_sink_close(struct sc_frame_sink *sink) {
|
||||||
|
struct sc_v4l2_sink *vs = DOWNCAST(sink);
|
||||||
|
sc_v4l2_sink_close(vs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
sc_v4l2_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||||
|
struct sc_v4l2_sink *vs = DOWNCAST(sink);
|
||||||
|
return sc_v4l2_sink_push(vs, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
||||||
|
struct size frame_size) {
|
||||||
|
vs->device_name = strdup(device_name);
|
||||||
|
if (!vs->device_name) {
|
||||||
|
LOGE("Could not strdup v4l2 device name");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
vs->frame_size = frame_size;
|
||||||
|
|
||||||
|
static const struct sc_frame_sink_ops ops = {
|
||||||
|
.open = sc_v4l2_frame_sink_open,
|
||||||
|
.close = sc_v4l2_frame_sink_close,
|
||||||
|
.push = sc_v4l2_frame_sink_push,
|
||||||
|
};
|
||||||
|
|
||||||
|
vs->frame_sink.ops = &ops;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs) {
|
||||||
|
free(vs->device_name);
|
||||||
|
}
|
||||||
39
app/src/v4l2_sink.h
Normal file
39
app/src/v4l2_sink.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#ifndef SC_V4L2_SINK_H
|
||||||
|
#define SC_V4L2_SINK_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include "coords.h"
|
||||||
|
#include "trait/frame_sink.h"
|
||||||
|
#include "video_buffer.h"
|
||||||
|
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
|
struct sc_v4l2_sink {
|
||||||
|
struct sc_frame_sink frame_sink; // frame sink trait
|
||||||
|
|
||||||
|
struct video_buffer vb;
|
||||||
|
AVFormatContext *format_ctx;
|
||||||
|
AVCodecContext *encoder_ctx;
|
||||||
|
|
||||||
|
char *device_name;
|
||||||
|
struct size frame_size;
|
||||||
|
|
||||||
|
sc_thread thread;
|
||||||
|
sc_mutex mutex;
|
||||||
|
sc_cond cond;
|
||||||
|
bool stopped;
|
||||||
|
bool header_written;
|
||||||
|
|
||||||
|
AVFrame *frame;
|
||||||
|
AVPacket *packet;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
||||||
|
struct size frame_size);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,113 +1,88 @@
|
|||||||
#include "video_buffer.h"
|
#include "video_buffer.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
#include <libavutil/avutil.h>
|
#include <libavutil/avutil.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "util/lock.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
|
video_buffer_init(struct video_buffer *vb) {
|
||||||
bool render_expired_frames) {
|
vb->pending_frame = av_frame_alloc();
|
||||||
vb->fps_counter = fps_counter;
|
if (!vb->pending_frame) {
|
||||||
|
return false;
|
||||||
if (!(vb->decoding_frame = av_frame_alloc())) {
|
|
||||||
goto error_0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(vb->rendering_frame = av_frame_alloc())) {
|
vb->tmp_frame = av_frame_alloc();
|
||||||
goto error_1;
|
if (!vb->tmp_frame) {
|
||||||
|
av_frame_free(&vb->pending_frame);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(vb->mutex = SDL_CreateMutex())) {
|
bool ok = sc_mutex_init(&vb->mutex);
|
||||||
goto error_2;
|
if (!ok) {
|
||||||
|
av_frame_free(&vb->pending_frame);
|
||||||
|
av_frame_free(&vb->tmp_frame);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
vb->render_expired_frames = render_expired_frames;
|
// there is initially no frame, so consider it has already been consumed
|
||||||
if (render_expired_frames) {
|
vb->pending_frame_consumed = true;
|
||||||
if (!(vb->rendering_frame_consumed_cond = SDL_CreateCond())) {
|
|
||||||
SDL_DestroyMutex(vb->mutex);
|
|
||||||
goto error_2;
|
|
||||||
}
|
|
||||||
// interrupted is not used if expired frames are not rendered
|
|
||||||
// since offering a frame will never block
|
|
||||||
vb->interrupted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// there is initially no rendering frame, so consider it has already been
|
|
||||||
// consumed
|
|
||||||
vb->rendering_frame_consumed = true;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
error_2:
|
|
||||||
av_frame_free(&vb->rendering_frame);
|
|
||||||
error_1:
|
|
||||||
av_frame_free(&vb->decoding_frame);
|
|
||||||
error_0:
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
video_buffer_destroy(struct video_buffer *vb) {
|
video_buffer_destroy(struct video_buffer *vb) {
|
||||||
if (vb->render_expired_frames) {
|
sc_mutex_destroy(&vb->mutex);
|
||||||
SDL_DestroyCond(vb->rendering_frame_consumed_cond);
|
av_frame_free(&vb->pending_frame);
|
||||||
}
|
av_frame_free(&vb->tmp_frame);
|
||||||
SDL_DestroyMutex(vb->mutex);
|
|
||||||
av_frame_free(&vb->rendering_frame);
|
|
||||||
av_frame_free(&vb->decoding_frame);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static inline void
|
||||||
video_buffer_swap_frames(struct video_buffer *vb) {
|
swap_frames(AVFrame **lhs, AVFrame **rhs) {
|
||||||
AVFrame *tmp = vb->decoding_frame;
|
AVFrame *tmp = *lhs;
|
||||||
vb->decoding_frame = vb->rendering_frame;
|
*lhs = *rhs;
|
||||||
vb->rendering_frame = tmp;
|
*rhs = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool
|
||||||
video_buffer_offer_decoded_frame(struct video_buffer *vb,
|
video_buffer_push(struct video_buffer *vb, const AVFrame *frame,
|
||||||
bool *previous_frame_skipped) {
|
bool *previous_frame_skipped) {
|
||||||
mutex_lock(vb->mutex);
|
sc_mutex_lock(&vb->mutex);
|
||||||
if (vb->render_expired_frames) {
|
|
||||||
// wait for the current (expired) frame to be consumed
|
// Use a temporary frame to preserve pending_frame in case of error.
|
||||||
while (!vb->rendering_frame_consumed && !vb->interrupted) {
|
// tmp_frame is an empty frame, no need to call av_frame_unref() beforehand.
|
||||||
cond_wait(vb->rendering_frame_consumed_cond, vb->mutex);
|
int r = av_frame_ref(vb->tmp_frame, frame);
|
||||||
}
|
if (r) {
|
||||||
} else if (!vb->rendering_frame_consumed) {
|
LOGE("Could not ref frame: %d", r);
|
||||||
fps_counter_add_skipped_frame(vb->fps_counter);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
video_buffer_swap_frames(vb);
|
// Now that av_frame_ref() succeeded, we can replace the previous
|
||||||
|
// pending_frame
|
||||||
|
swap_frames(&vb->pending_frame, &vb->tmp_frame);
|
||||||
|
av_frame_unref(vb->tmp_frame);
|
||||||
|
|
||||||
*previous_frame_skipped = !vb->rendering_frame_consumed;
|
if (previous_frame_skipped) {
|
||||||
vb->rendering_frame_consumed = false;
|
*previous_frame_skipped = !vb->pending_frame_consumed;
|
||||||
|
|
||||||
mutex_unlock(vb->mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
const AVFrame *
|
|
||||||
video_buffer_consume_rendered_frame(struct video_buffer *vb) {
|
|
||||||
assert(!vb->rendering_frame_consumed);
|
|
||||||
vb->rendering_frame_consumed = true;
|
|
||||||
fps_counter_add_rendered_frame(vb->fps_counter);
|
|
||||||
if (vb->render_expired_frames) {
|
|
||||||
// unblock video_buffer_offer_decoded_frame()
|
|
||||||
cond_signal(vb->rendering_frame_consumed_cond);
|
|
||||||
}
|
}
|
||||||
return vb->rendering_frame;
|
vb->pending_frame_consumed = false;
|
||||||
|
|
||||||
|
sc_mutex_unlock(&vb->mutex);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
video_buffer_interrupt(struct video_buffer *vb) {
|
video_buffer_consume(struct video_buffer *vb, AVFrame *dst) {
|
||||||
if (vb->render_expired_frames) {
|
sc_mutex_lock(&vb->mutex);
|
||||||
mutex_lock(vb->mutex);
|
assert(!vb->pending_frame_consumed);
|
||||||
vb->interrupted = true;
|
vb->pending_frame_consumed = true;
|
||||||
mutex_unlock(vb->mutex);
|
|
||||||
// wake up blocking wait
|
av_frame_move_ref(dst, vb->pending_frame);
|
||||||
cond_signal(vb->rendering_frame_consumed_cond);
|
// av_frame_move_ref() resets its source frame, so no need to call
|
||||||
}
|
// av_frame_unref()
|
||||||
|
|
||||||
|
sc_mutex_unlock(&vb->mutex);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +1,50 @@
|
|||||||
#ifndef VIDEO_BUFFER_H
|
#ifndef VIDEO_BUFFER_H
|
||||||
#define VIDEO_BUFFER_H
|
#define VIDEO_BUFFER_H
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include "common.h"
|
||||||
#include <SDL2/SDL_mutex.h>
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "fps_counter.h"
|
#include "fps_counter.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
|
||||||
// forward declarations
|
// forward declarations
|
||||||
typedef struct AVFrame AVFrame;
|
typedef struct AVFrame AVFrame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A video buffer holds 1 pending frame, which is the last frame received from
|
||||||
|
* the producer (typically, the decoder).
|
||||||
|
*
|
||||||
|
* If a pending frame has not been consumed when the producer pushes a new
|
||||||
|
* frame, then it is lost. The intent is to always provide access to the very
|
||||||
|
* last frame to minimize latency.
|
||||||
|
*
|
||||||
|
* The producer and the consumer typically do not live in the same thread.
|
||||||
|
* That's the reason why the callback on_frame_available() does not provide the
|
||||||
|
* frame as parameter: the consumer might post an event to its own thread to
|
||||||
|
* retrieve the pending frame from there, and that frame may have changed since
|
||||||
|
* the callback if producer pushed a new one in between.
|
||||||
|
*/
|
||||||
|
|
||||||
struct video_buffer {
|
struct video_buffer {
|
||||||
AVFrame *decoding_frame;
|
AVFrame *pending_frame;
|
||||||
AVFrame *rendering_frame;
|
AVFrame *tmp_frame; // To preserve the pending frame on error
|
||||||
SDL_mutex *mutex;
|
|
||||||
bool render_expired_frames;
|
sc_mutex mutex;
|
||||||
bool interrupted;
|
|
||||||
SDL_cond *rendering_frame_consumed_cond;
|
bool pending_frame_consumed;
|
||||||
bool rendering_frame_consumed;
|
|
||||||
struct fps_counter *fps_counter;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
|
video_buffer_init(struct video_buffer *vb);
|
||||||
bool render_expired_frames);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
video_buffer_destroy(struct video_buffer *vb);
|
video_buffer_destroy(struct video_buffer *vb);
|
||||||
|
|
||||||
// set the decoded frame as ready for rendering
|
bool
|
||||||
// this function locks frames->mutex during its execution
|
video_buffer_push(struct video_buffer *vb, const AVFrame *frame, bool *skipped);
|
||||||
// the output flag is set to report whether the previous frame has been skipped
|
|
||||||
void
|
|
||||||
video_buffer_offer_decoded_frame(struct video_buffer *vb,
|
|
||||||
bool *previous_frame_skipped);
|
|
||||||
|
|
||||||
// mark the rendering frame as consumed and return it
|
|
||||||
// MUST be called with frames->mutex locked!!!
|
|
||||||
// the caller is expected to render the returned frame to some texture before
|
|
||||||
// unlocking frames->mutex
|
|
||||||
const AVFrame *
|
|
||||||
video_buffer_consume_rendered_frame(struct video_buffer *vb);
|
|
||||||
|
|
||||||
// wake up and avoid any blocking call
|
|
||||||
void
|
void
|
||||||
video_buffer_interrupt(struct video_buffer *vb);
|
video_buffer_consume(struct video_buffer *vb, AVFrame *dst);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include "util/buffer_util.h"
|
#include "util/buffer_util.h"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "cli.h"
|
#include "cli.h"
|
||||||
#include "common.h"
|
|
||||||
#include "scrcpy.h"
|
#include "scrcpy.h"
|
||||||
|
|
||||||
static void test_flag_version(void) {
|
static void test_flag_version(void) {
|
||||||
@@ -50,14 +51,13 @@ static void test_options(void) {
|
|||||||
"--fullscreen",
|
"--fullscreen",
|
||||||
"--max-fps", "30",
|
"--max-fps", "30",
|
||||||
"--max-size", "1024",
|
"--max-size", "1024",
|
||||||
"--lock-video-orientation", "2",
|
"--lock-video-orientation=2", // optional arguments require '='
|
||||||
// "--no-control" is not compatible with "--turn-screen-off"
|
// "--no-control" is not compatible with "--turn-screen-off"
|
||||||
// "--no-display" is not compatible with "--fulscreen"
|
// "--no-display" is not compatible with "--fulscreen"
|
||||||
"--port", "1234:1236",
|
"--port", "1234:1236",
|
||||||
"--push-target", "/sdcard/Movies",
|
"--push-target", "/sdcard/Movies",
|
||||||
"--record", "file",
|
"--record", "file",
|
||||||
"--record-format", "mkv",
|
"--record-format", "mkv",
|
||||||
"--render-expired-frames",
|
|
||||||
"--serial", "0123456789abcdef",
|
"--serial", "0123456789abcdef",
|
||||||
"--show-touches",
|
"--show-touches",
|
||||||
"--turn-screen-off",
|
"--turn-screen-off",
|
||||||
@@ -86,7 +86,6 @@ static void test_options(void) {
|
|||||||
assert(!strcmp(opts->push_target, "/sdcard/Movies"));
|
assert(!strcmp(opts->push_target, "/sdcard/Movies"));
|
||||||
assert(!strcmp(opts->record_filename, "file"));
|
assert(!strcmp(opts->record_filename, "file"));
|
||||||
assert(opts->record_format == SC_RECORD_FORMAT_MKV);
|
assert(opts->record_format == SC_RECORD_FORMAT_MKV);
|
||||||
assert(opts->render_expired_frames);
|
|
||||||
assert(!strcmp(opts->serial, "0123456789abcdef"));
|
assert(!strcmp(opts->serial, "0123456789abcdef"));
|
||||||
assert(opts->show_touches);
|
assert(opts->show_touches);
|
||||||
assert(opts->turn_screen_off);
|
assert(opts->turn_screen_off);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@@ -15,7 +17,7 @@ static void test_serialize_inject_keycode(void) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 14);
|
assert(size == 14);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
@@ -37,7 +39,7 @@ static void test_serialize_inject_text(void) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 18);
|
assert(size == 18);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
@@ -57,7 +59,7 @@ static void test_serialize_inject_text_long(void) {
|
|||||||
msg.inject_text.text = text;
|
msg.inject_text.text = text;
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
||||||
|
|
||||||
unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
|
unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
|
||||||
@@ -93,7 +95,7 @@ static void test_serialize_inject_touch_event(void) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 28);
|
assert(size == 28);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
@@ -128,7 +130,7 @@ static void test_serialize_inject_scroll_event(void) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 21);
|
assert(size == 21);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
@@ -144,14 +146,18 @@ static void test_serialize_inject_scroll_event(void) {
|
|||||||
static void test_serialize_back_or_screen_on(void) {
|
static void test_serialize_back_or_screen_on(void) {
|
||||||
struct control_msg msg = {
|
struct control_msg msg = {
|
||||||
.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
||||||
|
.back_or_screen_on = {
|
||||||
|
.action = AKEY_EVENT_ACTION_UP,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 2);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
|
||||||
|
0x01, // AKEY_EVENT_ACTION_UP
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
@@ -162,7 +168,7 @@ static void test_serialize_expand_notification_panel(void) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
@@ -171,17 +177,32 @@ static void test_serialize_expand_notification_panel(void) {
|
|||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_serialize_collapse_notification_panel(void) {
|
static void test_serialize_expand_settings_panel(void) {
|
||||||
struct control_msg msg = {
|
struct control_msg msg = {
|
||||||
.type = CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
|
.type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
CONTROL_MSG_TYPE_COLLAPSE_NOTIFICATION_PANEL,
|
CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
|
||||||
|
};
|
||||||
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_serialize_collapse_panels(void) {
|
||||||
|
struct control_msg msg = {
|
||||||
|
.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
|
assert(size == 1);
|
||||||
|
|
||||||
|
const unsigned char expected[] = {
|
||||||
|
CONTROL_MSG_TYPE_COLLAPSE_PANELS,
|
||||||
};
|
};
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
@@ -192,7 +213,7 @@ static void test_serialize_get_clipboard(void) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
@@ -211,7 +232,7 @@ static void test_serialize_set_clipboard(void) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 19);
|
assert(size == 19);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
@@ -232,7 +253,7 @@ static void test_serialize_set_screen_power_mode(void) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 2);
|
assert(size == 2);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
@@ -248,7 +269,7 @@ static void test_serialize_rotate_device(void) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
size_t size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 1);
|
assert(size == 1);
|
||||||
|
|
||||||
const unsigned char expected[] = {
|
const unsigned char expected[] = {
|
||||||
@@ -268,7 +289,8 @@ int main(int argc, char *argv[]) {
|
|||||||
test_serialize_inject_scroll_event();
|
test_serialize_inject_scroll_event();
|
||||||
test_serialize_back_or_screen_on();
|
test_serialize_back_or_screen_on();
|
||||||
test_serialize_expand_notification_panel();
|
test_serialize_expand_notification_panel();
|
||||||
test_serialize_collapse_notification_panel();
|
test_serialize_expand_settings_panel();
|
||||||
|
test_serialize_collapse_panels();
|
||||||
test_serialize_get_clipboard();
|
test_serialize_get_clipboard();
|
||||||
test_serialize_set_clipboard();
|
test_serialize_set_clipboard();
|
||||||
test_serialize_set_screen_power_mode();
|
test_serialize_set_screen_power_mode();
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include "util/queue.h"
|
#include "util/queue.h"
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
#include "common.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <SDL2/SDL.h>
|
|
||||||
|
|
||||||
#include "util/str_util.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
@@ -136,7 +137,7 @@ static void test_strquote(void) {
|
|||||||
// add '"' at the beginning and the end
|
// add '"' at the beginning and the end
|
||||||
assert(!strcmp("\"abcde\"", out));
|
assert(!strcmp("\"abcde\"", out));
|
||||||
|
|
||||||
SDL_free(out);
|
free(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_utf8_truncate(void) {
|
static void test_utf8_truncate(void) {
|
||||||
@@ -286,6 +287,18 @@ static void test_parse_integer_with_suffix(void) {
|
|||||||
assert(!ok);
|
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[]) {
|
int main(int argc, char *argv[]) {
|
||||||
(void) argc;
|
(void) argc;
|
||||||
(void) argv;
|
(void) argv;
|
||||||
@@ -303,5 +316,6 @@ int main(int argc, char *argv[]) {
|
|||||||
test_parse_integer();
|
test_parse_integer();
|
||||||
test_parse_integers();
|
test_parse_integers();
|
||||||
test_parse_integer_with_suffix();
|
test_parse_integer_with_suffix();
|
||||||
|
test_strlist_contains();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
[binaries]
|
[binaries]
|
||||||
name = 'mingw'
|
name = 'mingw'
|
||||||
c = '/usr/bin/i686-w64-mingw32-gcc'
|
c = 'i686-w64-mingw32-gcc'
|
||||||
cpp = '/usr/bin/i686-w64-mingw32-g++'
|
cpp = 'i686-w64-mingw32-g++'
|
||||||
ar = '/usr/bin/i686-w64-mingw32-ar'
|
ar = 'i686-w64-mingw32-ar'
|
||||||
strip = '/usr/bin/i686-w64-mingw32-strip'
|
strip = 'i686-w64-mingw32-strip'
|
||||||
pkgconfig = '/usr/bin/i686-w64-mingw32-pkg-config'
|
pkgconfig = 'i686-w64-mingw32-pkg-config'
|
||||||
|
|
||||||
[host_machine]
|
[host_machine]
|
||||||
system = 'windows'
|
system = 'windows'
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
[binaries]
|
[binaries]
|
||||||
name = 'mingw'
|
name = 'mingw'
|
||||||
c = '/usr/bin/x86_64-w64-mingw32-gcc'
|
c = 'x86_64-w64-mingw32-gcc'
|
||||||
cpp = '/usr/bin/x86_64-w64-mingw32-g++'
|
cpp = 'x86_64-w64-mingw32-g++'
|
||||||
ar = '/usr/bin/x86_64-w64-mingw32-ar'
|
ar = 'x86_64-w64-mingw32-ar'
|
||||||
strip = '/usr/bin/x86_64-w64-mingw32-strip'
|
strip = 'x86_64-w64-mingw32-strip'
|
||||||
pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config'
|
pkgconfig = 'x86_64-w64-mingw32-pkg-config'
|
||||||
|
|
||||||
[host_machine]
|
[host_machine]
|
||||||
system = 'windows'
|
system = 'windows'
|
||||||
|
|||||||
@@ -1 +1,7 @@
|
|||||||
CreateObject("Wscript.Shell").Run "cmd /c scrcpy.exe", 0, false
|
strCommand = "cmd /c scrcpy.exe"
|
||||||
|
|
||||||
|
For Each Arg In WScript.Arguments
|
||||||
|
strCommand = strCommand & " """ & replace(Arg, """", """""""""") & """"
|
||||||
|
Next
|
||||||
|
|
||||||
|
CreateObject("Wscript.Shell").Run strCommand, 0, false
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
project('scrcpy', 'c',
|
project('scrcpy', 'c',
|
||||||
version: '1.17',
|
version: '1.18',
|
||||||
meson_version: '>= 0.48',
|
meson_version: '>= 0.48',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c11',
|
'c_std=c11',
|
||||||
'warning_level=2',
|
'warning_level=2',
|
||||||
|
'b_ndebug=if-release',
|
||||||
])
|
])
|
||||||
|
|
||||||
if get_option('compile_app')
|
if get_option('compile_app')
|
||||||
|
|||||||
@@ -3,6 +3,5 @@ option('compile_server', type: 'boolean', value: true, description: 'Build the s
|
|||||||
option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux')
|
option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux')
|
||||||
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
|
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
|
||||||
option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable')
|
option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable')
|
||||||
option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support')
|
|
||||||
option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached')
|
option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached')
|
||||||
option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")')
|
option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")')
|
||||||
|
|||||||
@@ -35,6 +35,6 @@ prepare-sdl2:
|
|||||||
SDL2-2.0.14
|
SDL2-2.0.14
|
||||||
|
|
||||||
prepare-adb:
|
prepare-adb:
|
||||||
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r30.0.5-windows.zip \
|
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r31.0.2-windows.zip \
|
||||||
549ba2bdc31f335eb8a504f005f77606a479cc216d6b64a3e8b64c780003661f \
|
d560cb8ded83ae04763b94632673481f14843a5969256569623cfeac82db4ba5 \
|
||||||
platform-tools
|
platform-tools
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ android {
|
|||||||
applicationId "com.genymobile.scrcpy"
|
applicationId "com.genymobile.scrcpy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 20
|
versionCode 11800
|
||||||
versionName "1.17"
|
versionName "1.18"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
SCRCPY_DEBUG=false
|
SCRCPY_DEBUG=false
|
||||||
SCRCPY_VERSION_NAME=1.17
|
SCRCPY_VERSION_NAME=1.18
|
||||||
|
|
||||||
PLATFORM=${ANDROID_PLATFORM:-30}
|
PLATFORM=${ANDROID_PLATFORM:-30}
|
||||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-30.0.0}
|
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-30.0.0}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ package com.genymobile.scrcpy;
|
|||||||
import com.genymobile.scrcpy.wrappers.ContentProvider;
|
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.Parcelable;
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@@ -15,23 +19,123 @@ public final class CleanUp {
|
|||||||
|
|
||||||
public static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar";
|
public static final String SERVER_PATH = "/data/local/tmp/scrcpy-server.jar";
|
||||||
|
|
||||||
|
// A simple struct to be passed from the main process to the cleanup process
|
||||||
|
public static class Config implements Parcelable {
|
||||||
|
|
||||||
|
public static final Creator<Config> CREATOR = new Creator<Config>() {
|
||||||
|
@Override
|
||||||
|
public Config createFromParcel(Parcel in) {
|
||||||
|
return new Config(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Config[] newArray(int size) {
|
||||||
|
return new Config[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final int FLAG_DISABLE_SHOW_TOUCHES = 1;
|
||||||
|
private static final int FLAG_RESTORE_NORMAL_POWER_MODE = 2;
|
||||||
|
private static final int FLAG_POWER_OFF_SCREEN = 4;
|
||||||
|
|
||||||
|
private int displayId;
|
||||||
|
|
||||||
|
// Restore the value (between 0 and 7), -1 to not restore
|
||||||
|
// <https://developer.android.com/reference/android/provider/Settings.Global#STAY_ON_WHILE_PLUGGED_IN>
|
||||||
|
private int restoreStayOn = -1;
|
||||||
|
|
||||||
|
private boolean disableShowTouches;
|
||||||
|
private boolean restoreNormalPowerMode;
|
||||||
|
private boolean powerOffScreen;
|
||||||
|
|
||||||
|
public Config() {
|
||||||
|
// Default constructor, the fields are initialized by CleanUp.configure()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Config(Parcel in) {
|
||||||
|
displayId = in.readInt();
|
||||||
|
restoreStayOn = in.readInt();
|
||||||
|
byte options = in.readByte();
|
||||||
|
disableShowTouches = (options & FLAG_DISABLE_SHOW_TOUCHES) != 0;
|
||||||
|
restoreNormalPowerMode = (options & FLAG_RESTORE_NORMAL_POWER_MODE) != 0;
|
||||||
|
powerOffScreen = (options & FLAG_POWER_OFF_SCREEN) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeInt(displayId);
|
||||||
|
dest.writeInt(restoreStayOn);
|
||||||
|
byte options = 0;
|
||||||
|
if (disableShowTouches) {
|
||||||
|
options |= FLAG_DISABLE_SHOW_TOUCHES;
|
||||||
|
}
|
||||||
|
if (restoreNormalPowerMode) {
|
||||||
|
options |= FLAG_RESTORE_NORMAL_POWER_MODE;
|
||||||
|
}
|
||||||
|
if (powerOffScreen) {
|
||||||
|
options |= FLAG_POWER_OFF_SCREEN;
|
||||||
|
}
|
||||||
|
dest.writeByte(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasWork() {
|
||||||
|
return disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode || powerOffScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] serialize() {
|
||||||
|
Parcel parcel = Parcel.obtain();
|
||||||
|
writeToParcel(parcel, 0);
|
||||||
|
byte[] bytes = parcel.marshall();
|
||||||
|
parcel.recycle();
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Config deserialize(byte[] bytes) {
|
||||||
|
Parcel parcel = Parcel.obtain();
|
||||||
|
parcel.unmarshall(bytes, 0, bytes.length);
|
||||||
|
parcel.setDataPosition(0);
|
||||||
|
return CREATOR.createFromParcel(parcel);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Config fromBase64(String base64) {
|
||||||
|
byte[] bytes = Base64.decode(base64, Base64.NO_WRAP);
|
||||||
|
return deserialize(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toBase64() {
|
||||||
|
byte[] bytes = serialize();
|
||||||
|
return Base64.encodeToString(bytes, Base64.NO_WRAP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private CleanUp() {
|
private CleanUp() {
|
||||||
// not instantiable
|
// not instantiable
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException {
|
public static void configure(int displayId, int restoreStayOn, boolean disableShowTouches, boolean restoreNormalPowerMode, boolean powerOffScreen)
|
||||||
boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode;
|
throws IOException {
|
||||||
if (needProcess) {
|
Config config = new Config();
|
||||||
startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode);
|
config.displayId = displayId;
|
||||||
|
config.disableShowTouches = disableShowTouches;
|
||||||
|
config.restoreStayOn = restoreStayOn;
|
||||||
|
config.restoreNormalPowerMode = restoreNormalPowerMode;
|
||||||
|
config.powerOffScreen = powerOffScreen;
|
||||||
|
|
||||||
|
if (config.hasWork()) {
|
||||||
|
startProcess(config);
|
||||||
} else {
|
} else {
|
||||||
// There is no additional clean up to do when scrcpy dies
|
// There is no additional clean up to do when scrcpy dies
|
||||||
unlinkSelf();
|
unlinkSelf();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void startProcess(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException {
|
private static void startProcess(Config config) throws IOException {
|
||||||
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf(
|
String[] cmd = {"app_process", "/", CleanUp.class.getName(), config.toBase64()};
|
||||||
restoreStayOn), String.valueOf(restoreNormalPowerMode)};
|
|
||||||
|
|
||||||
ProcessBuilder builder = new ProcessBuilder(cmd);
|
ProcessBuilder builder = new ProcessBuilder(cmd);
|
||||||
builder.environment().put("CLASSPATH", SERVER_PATH);
|
builder.environment().put("CLASSPATH", SERVER_PATH);
|
||||||
@@ -58,27 +162,28 @@ public final class CleanUp {
|
|||||||
|
|
||||||
Ln.i("Cleaning up");
|
Ln.i("Cleaning up");
|
||||||
|
|
||||||
boolean disableShowTouches = Boolean.parseBoolean(args[0]);
|
Config config = Config.fromBase64(args[0]);
|
||||||
int restoreStayOn = Integer.parseInt(args[1]);
|
|
||||||
boolean restoreNormalPowerMode = Boolean.parseBoolean(args[2]);
|
|
||||||
|
|
||||||
if (disableShowTouches || restoreStayOn != -1) {
|
if (config.disableShowTouches || config.restoreStayOn != -1) {
|
||||||
ServiceManager serviceManager = new ServiceManager();
|
ServiceManager serviceManager = new ServiceManager();
|
||||||
try (ContentProvider settings = serviceManager.getActivityManager().createSettingsProvider()) {
|
try (ContentProvider settings = serviceManager.getActivityManager().createSettingsProvider()) {
|
||||||
if (disableShowTouches) {
|
if (config.disableShowTouches) {
|
||||||
Ln.i("Disabling \"show touches\"");
|
Ln.i("Disabling \"show touches\"");
|
||||||
settings.putValue(ContentProvider.TABLE_SYSTEM, "show_touches", "0");
|
settings.putValue(ContentProvider.TABLE_SYSTEM, "show_touches", "0");
|
||||||
}
|
}
|
||||||
if (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(restoreStayOn));
|
settings.putValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (restoreNormalPowerMode) {
|
|
||||||
Ln.i("Restoring normal power mode");
|
|
||||||
if (Device.isScreenOn()) {
|
if (Device.isScreenOn()) {
|
||||||
|
if (config.powerOffScreen) {
|
||||||
|
Ln.i("Power off screen");
|
||||||
|
Device.powerOffScreen(config.displayId);
|
||||||
|
} else if (config.restoreNormalPowerMode) {
|
||||||
|
Ln.i("Restoring normal power mode");
|
||||||
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
|
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,12 @@ public final class ControlMessage {
|
|||||||
public static final int TYPE_INJECT_SCROLL_EVENT = 3;
|
public static final int TYPE_INJECT_SCROLL_EVENT = 3;
|
||||||
public static final int TYPE_BACK_OR_SCREEN_ON = 4;
|
public static final int TYPE_BACK_OR_SCREEN_ON = 4;
|
||||||
public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5;
|
public static final int TYPE_EXPAND_NOTIFICATION_PANEL = 5;
|
||||||
public static final int TYPE_COLLAPSE_NOTIFICATION_PANEL = 6;
|
public static final int TYPE_EXPAND_SETTINGS_PANEL = 6;
|
||||||
public static final int TYPE_GET_CLIPBOARD = 7;
|
public static final int TYPE_COLLAPSE_PANELS = 7;
|
||||||
public static final int TYPE_SET_CLIPBOARD = 8;
|
public static final int TYPE_GET_CLIPBOARD = 8;
|
||||||
public static final int TYPE_SET_SCREEN_POWER_MODE = 9;
|
public static final int TYPE_SET_CLIPBOARD = 9;
|
||||||
public static final int TYPE_ROTATE_DEVICE = 10;
|
public static final int TYPE_SET_SCREEN_POWER_MODE = 10;
|
||||||
|
public static final int TYPE_ROTATE_DEVICE = 11;
|
||||||
|
|
||||||
private int type;
|
private int type;
|
||||||
private String text;
|
private String text;
|
||||||
@@ -71,6 +72,13 @@ public final class ControlMessage {
|
|||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ControlMessage createBackOrScreenOn(int action) {
|
||||||
|
ControlMessage msg = new ControlMessage();
|
||||||
|
msg.type = TYPE_BACK_OR_SCREEN_ON;
|
||||||
|
msg.action = action;
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
public static ControlMessage createSetClipboard(String text, boolean paste) {
|
public static ControlMessage createSetClipboard(String text, boolean paste) {
|
||||||
ControlMessage msg = new ControlMessage();
|
ControlMessage msg = new ControlMessage();
|
||||||
msg.type = TYPE_SET_CLIPBOARD;
|
msg.type = TYPE_SET_CLIPBOARD;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ public class ControlMessageReader {
|
|||||||
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
|
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
|
||||||
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
||||||
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||||
|
static final int BACK_OR_SCREEN_ON_LENGTH = 1;
|
||||||
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
||||||
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1;
|
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 1;
|
||||||
|
|
||||||
@@ -66,15 +67,18 @@ public class ControlMessageReader {
|
|||||||
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
||||||
msg = parseInjectScrollEvent();
|
msg = parseInjectScrollEvent();
|
||||||
break;
|
break;
|
||||||
|
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
||||||
|
msg = parseBackOrScreenOnEvent();
|
||||||
|
break;
|
||||||
case ControlMessage.TYPE_SET_CLIPBOARD:
|
case ControlMessage.TYPE_SET_CLIPBOARD:
|
||||||
msg = parseSetClipboard();
|
msg = parseSetClipboard();
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
|
||||||
msg = parseSetScreenPowerMode();
|
msg = parseSetScreenPowerMode();
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
|
||||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
|
||||||
|
case ControlMessage.TYPE_COLLAPSE_PANELS:
|
||||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||||
msg = ControlMessage.createEmpty(type);
|
msg = ControlMessage.createEmpty(type);
|
||||||
@@ -150,6 +154,14 @@ public class ControlMessageReader {
|
|||||||
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll);
|
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ControlMessage parseBackOrScreenOnEvent() {
|
||||||
|
if (buffer.remaining() < BACK_OR_SCREEN_ON_LENGTH) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int action = toUnsigned(buffer.get());
|
||||||
|
return ControlMessage.createBackOrScreenOn(action);
|
||||||
|
}
|
||||||
|
|
||||||
private ControlMessage parseSetClipboard() {
|
private ControlMessage parseSetClipboard() {
|
||||||
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
|
if (buffer.remaining() < SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
|
|
||||||
public class Controller {
|
public class Controller {
|
||||||
|
|
||||||
private static final int DEVICE_ID_VIRTUAL = -1;
|
private static final int DEFAULT_DEVICE_ID = 0;
|
||||||
|
|
||||||
private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor();
|
private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ public class Controller {
|
|||||||
|
|
||||||
MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
|
MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
|
||||||
coords.orientation = 0;
|
coords.orientation = 0;
|
||||||
coords.size = 1;
|
coords.size = 0;
|
||||||
|
|
||||||
pointerProperties[i] = props;
|
pointerProperties[i] = props;
|
||||||
pointerCoords[i] = coords;
|
pointerCoords[i] = coords;
|
||||||
@@ -55,7 +55,7 @@ public class Controller {
|
|||||||
public void control() throws IOException {
|
public void control() throws IOException {
|
||||||
// on start, power on the device
|
// on start, power on the device
|
||||||
if (!Device.isScreenOn()) {
|
if (!Device.isScreenOn()) {
|
||||||
device.injectKeycode(KeyEvent.KEYCODE_POWER);
|
device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER);
|
||||||
|
|
||||||
// dirty hack
|
// dirty hack
|
||||||
// After POWER is injected, the device is powered on asynchronously.
|
// After POWER is injected, the device is powered on asynchronously.
|
||||||
@@ -101,13 +101,16 @@ public class Controller {
|
|||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
||||||
if (device.supportsInputEvents()) {
|
if (device.supportsInputEvents()) {
|
||||||
pressBackOrTurnScreenOn();
|
pressBackOrTurnScreenOn(msg.getAction());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
Device.expandNotificationPanel();
|
Device.expandNotificationPanel();
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_COLLAPSE_NOTIFICATION_PANEL:
|
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
|
||||||
|
Device.expandSettingsPanel();
|
||||||
|
break;
|
||||||
|
case ControlMessage.TYPE_COLLAPSE_PANELS:
|
||||||
Device.collapsePanels();
|
Device.collapsePanels();
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_GET_CLIPBOARD:
|
case ControlMessage.TYPE_GET_CLIPBOARD:
|
||||||
@@ -208,9 +211,13 @@ public class Controller {
|
|||||||
// Right-click and middle-click only work if the source is a mouse
|
// Right-click and middle-click only work if the source is a mouse
|
||||||
boolean nonPrimaryButtonPressed = (buttons & ~MotionEvent.BUTTON_PRIMARY) != 0;
|
boolean nonPrimaryButtonPressed = (buttons & ~MotionEvent.BUTTON_PRIMARY) != 0;
|
||||||
int source = nonPrimaryButtonPressed ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_TOUCHSCREEN;
|
int source = nonPrimaryButtonPressed ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_TOUCHSCREEN;
|
||||||
|
if (source != InputDevice.SOURCE_MOUSE) {
|
||||||
|
// Buttons must not be set for touch events
|
||||||
|
buttons = 0;
|
||||||
|
}
|
||||||
|
|
||||||
MotionEvent event = MotionEvent
|
MotionEvent event = MotionEvent
|
||||||
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0, source,
|
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source,
|
||||||
0);
|
0);
|
||||||
return device.injectEvent(event);
|
return device.injectEvent(event);
|
||||||
}
|
}
|
||||||
@@ -233,7 +240,7 @@ public class Controller {
|
|||||||
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
|
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
|
||||||
|
|
||||||
MotionEvent event = MotionEvent
|
MotionEvent event = MotionEvent
|
||||||
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
|
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0,
|
||||||
InputDevice.SOURCE_TOUCHSCREEN, 0);
|
InputDevice.SOURCE_TOUCHSCREEN, 0);
|
||||||
return device.injectEvent(event);
|
return device.injectEvent(event);
|
||||||
}
|
}
|
||||||
@@ -251,12 +258,22 @@ public class Controller {
|
|||||||
}, 200, TimeUnit.MILLISECONDS);
|
}, 200, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean pressBackOrTurnScreenOn() {
|
private boolean pressBackOrTurnScreenOn(int action) {
|
||||||
int keycode = Device.isScreenOn() ? KeyEvent.KEYCODE_BACK : KeyEvent.KEYCODE_POWER;
|
if (Device.isScreenOn()) {
|
||||||
if (keepPowerModeOff && keycode == KeyEvent.KEYCODE_POWER) {
|
return device.injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Screen is off
|
||||||
|
// Only press POWER on ACTION_DOWN
|
||||||
|
if (action != KeyEvent.ACTION_DOWN) {
|
||||||
|
// do nothing,
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keepPowerModeOff) {
|
||||||
schedulePowerModeOff();
|
schedulePowerModeOff();
|
||||||
}
|
}
|
||||||
return device.injectKeycode(keycode);
|
return device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean setClipboard(String text, boolean paste) {
|
private boolean setClipboard(String text, boolean paste) {
|
||||||
@@ -267,7 +284,7 @@ public class Controller {
|
|||||||
|
|
||||||
// On Android >= 7, also press the PASTE key if requested
|
// On Android >= 7, also press the PASTE key if requested
|
||||||
if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
|
if (paste && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && device.supportsInputEvents()) {
|
||||||
device.injectKeycode(KeyEvent.KEYCODE_PASTE);
|
device.pressReleaseKeycode(KeyEvent.KEYCODE_PASTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ok;
|
return ok;
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ public final class Device {
|
|||||||
public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF;
|
public static final int POWER_MODE_OFF = SurfaceControl.POWER_MODE_OFF;
|
||||||
public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL;
|
public static final int POWER_MODE_NORMAL = SurfaceControl.POWER_MODE_NORMAL;
|
||||||
|
|
||||||
|
public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1;
|
||||||
|
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();
|
||||||
|
|
||||||
public interface RotationListener {
|
public interface RotationListener {
|
||||||
@@ -153,12 +156,16 @@ public final class Device {
|
|||||||
return Build.MODEL;
|
return Build.MODEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean supportsInputEvents(int displayId) {
|
||||||
|
return displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean supportsInputEvents() {
|
public boolean supportsInputEvents() {
|
||||||
return supportsInputEvents;
|
return supportsInputEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean injectEvent(InputEvent inputEvent, int mode) {
|
public static boolean injectEvent(InputEvent inputEvent, int displayId) {
|
||||||
if (!supportsInputEvents()) {
|
if (!supportsInputEvents(displayId)) {
|
||||||
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
|
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,22 +173,30 @@ public final class Device {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, mode);
|
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean injectEvent(InputEvent event) {
|
public boolean injectEvent(InputEvent event) {
|
||||||
return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
|
return injectEvent(event, displayId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
|
public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId) {
|
||||||
long now = SystemClock.uptimeMillis();
|
long now = SystemClock.uptimeMillis();
|
||||||
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
|
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
|
||||||
InputDevice.SOURCE_KEYBOARD);
|
InputDevice.SOURCE_KEYBOARD);
|
||||||
return injectEvent(event);
|
return injectEvent(event, displayId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean injectKeycode(int keyCode) {
|
public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
|
||||||
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
|
return injectKeyEvent(action, keyCode, repeat, metaState, displayId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean pressReleaseKeycode(int keyCode, int displayId) {
|
||||||
|
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0, displayId) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean pressReleaseKeycode(int keyCode) {
|
||||||
|
return pressReleaseKeycode(keyCode, displayId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isScreenOn() {
|
public static boolean isScreenOn() {
|
||||||
@@ -200,6 +215,10 @@ public final class Device {
|
|||||||
SERVICE_MANAGER.getStatusBarManager().expandNotificationsPanel();
|
SERVICE_MANAGER.getStatusBarManager().expandNotificationsPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void expandSettingsPanel() {
|
||||||
|
SERVICE_MANAGER.getStatusBarManager().expandSettingsPanel();
|
||||||
|
}
|
||||||
|
|
||||||
public static void collapsePanels() {
|
public static void collapsePanels() {
|
||||||
SERVICE_MANAGER.getStatusBarManager().collapsePanels();
|
SERVICE_MANAGER.getStatusBarManager().collapsePanels();
|
||||||
}
|
}
|
||||||
@@ -249,6 +268,13 @@ public final class Device {
|
|||||||
return SurfaceControl.setDisplayPowerMode(d, mode);
|
return SurfaceControl.setDisplayPowerMode(d, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean powerOffScreen(int displayId) {
|
||||||
|
if (!isScreenOn()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled).
|
* Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public final class Ln {
|
|||||||
private static final String PREFIX = "[server] ";
|
private static final String PREFIX = "[server] ";
|
||||||
|
|
||||||
enum Level {
|
enum Level {
|
||||||
DEBUG, INFO, WARN, ERROR
|
VERBOSE, DEBUG, INFO, WARN, ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Level threshold = Level.INFO;
|
private static Level threshold = Level.INFO;
|
||||||
@@ -36,6 +36,13 @@ public final class Ln {
|
|||||||
return level.ordinal() >= threshold.ordinal();
|
return level.ordinal() >= threshold.ordinal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void v(String message) {
|
||||||
|
if (isEnabled(Level.VERBOSE)) {
|
||||||
|
Log.v(TAG, message);
|
||||||
|
System.out.println(PREFIX + "VERBOSE: " + message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void d(String message) {
|
public static void d(String message) {
|
||||||
if (isEnabled(Level.DEBUG)) {
|
if (isEnabled(Level.DEBUG)) {
|
||||||
Log.d(TAG, message);
|
Log.d(TAG, message);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ public class Options {
|
|||||||
private boolean stayAwake;
|
private boolean stayAwake;
|
||||||
private String codecOptions;
|
private String codecOptions;
|
||||||
private String encoderName;
|
private String encoderName;
|
||||||
|
private boolean powerOffScreenOnClose;
|
||||||
|
|
||||||
public Ln.Level getLogLevel() {
|
public Ln.Level getLogLevel() {
|
||||||
return logLevel;
|
return logLevel;
|
||||||
@@ -129,4 +130,12 @@ public class Options {
|
|||||||
public void setEncoderName(String encoderName) {
|
public void setEncoderName(String encoderName) {
|
||||||
this.encoderName = encoderName;
|
this.encoderName = encoderName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) {
|
||||||
|
this.powerOffScreenOnClose = powerOffScreenOnClose;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getPowerOffScreenOnClose() {
|
||||||
|
return this.powerOffScreenOnClose;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.media.MediaCodec;
|
|||||||
import android.media.MediaCodecInfo;
|
import android.media.MediaCodecInfo;
|
||||||
import android.media.MediaCodecList;
|
import android.media.MediaCodecList;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
|
||||||
@@ -225,7 +226,11 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static IBinder createDisplay() {
|
private static IBinder createDisplay() {
|
||||||
return SurfaceControl.createDisplay("scrcpy", true);
|
// Since Android 12 (preview), secure displays could not be created with shell permissions anymore.
|
||||||
|
// On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S".
|
||||||
|
boolean secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S"
|
||||||
|
.equals(Build.VERSION.CODENAME));
|
||||||
|
return SurfaceControl.createDisplay("scrcpy", secure);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void configure(MediaCodec codec, MediaFormat format) {
|
private static void configure(MediaCodec codec, MediaFormat format) {
|
||||||
|
|||||||
@@ -82,6 +82,12 @@ public final class ScreenInfo {
|
|||||||
|
|
||||||
public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize, int lockedVideoOrientation) {
|
public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize, int lockedVideoOrientation) {
|
||||||
int rotation = displayInfo.getRotation();
|
int rotation = displayInfo.getRotation();
|
||||||
|
|
||||||
|
if (lockedVideoOrientation == Device.LOCK_VIDEO_ORIENTATION_INITIAL) {
|
||||||
|
// The user requested to lock the video orientation to the current orientation
|
||||||
|
lockedVideoOrientation = rotation;
|
||||||
|
}
|
||||||
|
|
||||||
Size deviceSize = displayInfo.getSize();
|
Size deviceSize = displayInfo.getSize();
|
||||||
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
|
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
|
||||||
if (crop != null) {
|
if (crop != null) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user