Compare commits
18 Commits
server_thr
...
hid.13
Author | SHA1 | Date | |
---|---|---|---|
f595d16cde | |||
74fb32a584 | |||
2f29b2e25d | |||
710f7e1a7c | |||
f80c5107db | |||
0ed00c2e40 | |||
874a4967a4 | |||
a52779ae6b | |||
c8f65647f2 | |||
b89c23b7f5 | |||
7f3d2eded7 | |||
1d4e15f0d5 | |||
ee55118282 | |||
4c6096388e | |||
691bdb925f | |||
96e3963afc | |||
63b2b5ca4e | |||
a846c01883 |
11
BUILD.md
11
BUILD.md
@ -14,8 +14,7 @@ First, you need to install the required packages:
|
|||||||
# for Debian/Ubuntu
|
# for Debian/Ubuntu
|
||||||
sudo apt install ffmpeg libsdl2-2.0-0 adb wget \
|
sudo apt install ffmpeg libsdl2-2.0-0 adb wget \
|
||||||
gcc git pkg-config meson ninja-build libsdl2-dev \
|
gcc git pkg-config meson ninja-build libsdl2-dev \
|
||||||
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
|
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev
|
||||||
libusb-1.0-0 libusb-dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then clone the repo and execute the installation script
|
Then clone the repo and execute the installation script
|
||||||
@ -161,7 +160,8 @@ install the required packages:
|
|||||||
```bash
|
```bash
|
||||||
# runtime dependencies
|
# runtime dependencies
|
||||||
pacman -S mingw-w64-x86_64-SDL2 \
|
pacman -S mingw-w64-x86_64-SDL2 \
|
||||||
mingw-w64-x86_64-ffmpeg
|
mingw-w64-x86_64-ffmpeg \
|
||||||
|
mingw-w64-x86_64-libusb
|
||||||
|
|
||||||
# client build dependencies
|
# client build dependencies
|
||||||
pacman -S mingw-w64-x86_64-make \
|
pacman -S mingw-w64-x86_64-make \
|
||||||
@ -175,7 +175,8 @@ For a 32 bits version, replace `x86_64` by `i686`:
|
|||||||
```bash
|
```bash
|
||||||
# runtime dependencies
|
# runtime dependencies
|
||||||
pacman -S mingw-w64-i686-SDL2 \
|
pacman -S mingw-w64-i686-SDL2 \
|
||||||
mingw-w64-i686-ffmpeg
|
mingw-w64-i686-ffmpeg \
|
||||||
|
mingw-w64-i686-libusb
|
||||||
|
|
||||||
# client build dependencies
|
# client build dependencies
|
||||||
pacman -S mingw-w64-i686-make \
|
pacman -S mingw-w64-i686-make \
|
||||||
@ -199,7 +200,7 @@ Install the packages with [Homebrew]:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# runtime dependencies
|
# runtime dependencies
|
||||||
brew install sdl2 ffmpeg
|
brew install sdl2 ffmpeg libusb
|
||||||
|
|
||||||
# client build dependencies
|
# client build dependencies
|
||||||
brew install pkg-config meson
|
brew install pkg-config meson
|
||||||
|
18
FAQ.it.md
18
FAQ.it.md
@ -140,6 +140,24 @@ Potresti anche dover configurare il [comportamento di ridimensionamento][scaling
|
|||||||
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
|
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
|
||||||
|
|
||||||
|
|
||||||
|
### Problema con Wayland
|
||||||
|
|
||||||
|
Per impostazione predefinita, SDL utilizza x11 su Linux. Il [video driver] può essere cambiato attraversio la variabile d'ambiente `SDL_VIDEODRIVER`:
|
||||||
|
|
||||||
|
[video driver]: https://wiki.libsdl.org/FAQUsingSDL#how_do_i_choose_a_specific_video_driver
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export SDL_VIDEODRIVER=wayland
|
||||||
|
scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
Su alcune distribuzioni (almeno Fedora), il pacchetto `libdecor` deve essere installato manualmente.
|
||||||
|
|
||||||
|
Vedi le issues [#2554] e [#2559].
|
||||||
|
|
||||||
|
[#2554]: https://github.com/Genymobile/scrcpy/issues/2554
|
||||||
|
[#2559]: https://github.com/Genymobile/scrcpy/issues/2559
|
||||||
|
|
||||||
|
|
||||||
### Crash del compositore KWin
|
### Crash del compositore KWin
|
||||||
|
|
||||||
|
12
FAQ.md
12
FAQ.md
@ -118,17 +118,13 @@ In developer options, enable:
|
|||||||
|
|
||||||
### Special characters do not work
|
### Special characters do not work
|
||||||
|
|
||||||
The default text injection method is [limited to ASCII characters][text-input].
|
Injecting text input is [limited to ASCII characters][text-input]. A trick
|
||||||
A trick allows to also inject some [accented characters][accented-characters],
|
allows to also inject some [accented characters][accented-characters], but
|
||||||
but that's all. See [#37].
|
that's all. See [#37].
|
||||||
|
|
||||||
Since scrcpy v1.20 on Linux, it is possible to simulate a [physical
|
|
||||||
keyboard][hid] (HID).
|
|
||||||
|
|
||||||
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
|
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
|
||||||
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
|
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
|
||||||
[#37]: https://github.com/Genymobile/scrcpy/issues/37
|
[#37]: https://github.com/Genymobile/scrcpy/issues/37
|
||||||
[hid]: README.md#physical-keyboard-simulation-hid
|
|
||||||
|
|
||||||
|
|
||||||
## Client issues
|
## Client issues
|
||||||
@ -262,6 +258,6 @@ to add some arguments.
|
|||||||
|
|
||||||
This FAQ is available in other languages:
|
This FAQ is available in other languages:
|
||||||
|
|
||||||
- [Italiano (Italiano, `it`) - v1.17](FAQ.it.md)
|
- [Italiano (Italiano, `it`) - v1.19](FAQ.it.md)
|
||||||
- [한국어 (Korean, `ko`) - v1.11](FAQ.ko.md)
|
- [한국어 (Korean, `ko`) - v1.11](FAQ.ko.md)
|
||||||
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.18](FAQ.zh-Hans.md)
|
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.18](FAQ.zh-Hans.md)
|
||||||
|
121
README.it.md
121
README.it.md
@ -1,6 +1,6 @@
|
|||||||
_Apri il [README](README.md) originale e sempre aggiornato._
|
_Apri il [README](README.md) originale e sempre aggiornato._
|
||||||
|
|
||||||
# scrcpy (v1.17)
|
# scrcpy (v1.19)
|
||||||
|
|
||||||
Questa applicazione fornisce la visualizzazione e il controllo dei dispositivi Android collegati via USB (o [via TCP/IP][article-tcpip]). Non richiede alcun accesso _root_.
|
Questa applicazione fornisce la visualizzazione e il controllo dei dispositivi Android collegati via USB (o [via TCP/IP][article-tcpip]). Non richiede alcun accesso _root_.
|
||||||
Funziona su _GNU/Linux_, _Windows_ e _macOS_.
|
Funziona su _GNU/Linux_, _Windows_ e _macOS_.
|
||||||
@ -205,10 +205,11 @@ Se anche `--max-size` è specificata, il ridimensionamento è applicato dopo il
|
|||||||
Per bloccare l'orientamento della trasmissione:
|
Per bloccare l'orientamento della trasmissione:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --lock-video-orientation 0 # orientamento naturale
|
scrcpy --lock-video-orientation # orientamento iniziale (corrente)
|
||||||
scrcpy --lock-video-orientation 1 # 90° antiorario
|
scrcpy --lock-video-orientation=0 # orientamento naturale
|
||||||
scrcpy --lock-video-orientation 2 # 180°
|
scrcpy --lock-video-orientation=1 # 90° antiorario
|
||||||
scrcpy --lock-video-orientation 3 # 90° orario
|
scrcpy --lock-video-orientation=2 # 180°
|
||||||
|
scrcpy --lock-video-orientation=3 # 90° orario
|
||||||
```
|
```
|
||||||
|
|
||||||
Questo influisce sull'orientamento della registrazione.
|
Questo influisce sull'orientamento della registrazione.
|
||||||
@ -231,7 +232,9 @@ Per elencare i codificatori disponibili puoi immettere un nome di codificatore n
|
|||||||
scrcpy --encoder _
|
scrcpy --encoder _
|
||||||
```
|
```
|
||||||
|
|
||||||
### Registrazione
|
### Cattura
|
||||||
|
|
||||||
|
#### Registrazione
|
||||||
|
|
||||||
È possibile registrare lo schermo durante la trasmissione:
|
È possibile registrare lo schermo durante la trasmissione:
|
||||||
|
|
||||||
@ -253,6 +256,75 @@ I "fotogrammi saltati" sono registrati nonostante non siano mostrati in tempo re
|
|||||||
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
||||||
|
|
||||||
|
|
||||||
|
#### v4l2loopback
|
||||||
|
|
||||||
|
Su Linux è possibile inviare il flusso video ad un dispositivo v4l2 loopback, cosicchè un dispositivo Android possa essere aperto come una webcam da qualsiasi strumento compatibile con v4l2.
|
||||||
|
|
||||||
|
Il modulo `v4l2loopback` deve essere installato:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install v4l2loopback-dkms
|
||||||
|
```
|
||||||
|
|
||||||
|
Per creare un dispositvo v4l2:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo modprobe v4l2loopback
|
||||||
|
```
|
||||||
|
|
||||||
|
Questo creerà un nuovo dispositivo video in `/dev/videoN` dove `N` è un intero (più [opzioni](https://github.com/umlaeute/v4l2loopback#options) sono disponibili per crere più dispositivi o dispositivi con ID specifici).
|
||||||
|
|
||||||
|
Per elencare i dispositvi attivati:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# necessita del pacchetto v4l-utils
|
||||||
|
v4l2-ctl --list-devices
|
||||||
|
|
||||||
|
# semplice ma potrebbe essere sufficiente
|
||||||
|
ls /dev/video*
|
||||||
|
```
|
||||||
|
|
||||||
|
Per avviare scrcpy utilizzando un v4l2 sink:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --v4l2-sink=/dev/videoN
|
||||||
|
scrcpy --v4l2-sink=/dev/videoN --no-display # disabilita la finestra di trasmissione
|
||||||
|
scrcpy --v4l2-sink=/dev/videoN -N # versione corta
|
||||||
|
```
|
||||||
|
|
||||||
|
(sostituisci `N` con l'ID del dispositivo, controlla con `ls /dev/video*`)
|
||||||
|
|
||||||
|
Una volta abilitato, puoi aprire il tuo flusso video con uno strumento compatibile con v4l2:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ffplay -i /dev/videoN
|
||||||
|
vlc v4l2:///dev/videoN # VLC potrebbe aggiungere del ritardo per il buffer
|
||||||
|
```
|
||||||
|
|
||||||
|
Per esempio potresti catturare il video in [OBS].
|
||||||
|
|
||||||
|
[OBS]: https://obsproject.com/
|
||||||
|
|
||||||
|
|
||||||
|
#### Buffering
|
||||||
|
|
||||||
|
È possibile aggiungere del buffer. Questo aumenta la latenza ma riduce il jitter (vedi [#2464]).
|
||||||
|
|
||||||
|
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
|
||||||
|
|
||||||
|
L'opzione è disponibile per il buffer della visualizzazione:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --display-buffer=50 # aggiungi 50 ms di buffer per la visualizzazione
|
||||||
|
```
|
||||||
|
|
||||||
|
e per il V4L2 sink:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --v4l2-buffer=500 # aggiungi 50 ms di buffer per il v4l2 sink
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Connessione
|
### Connessione
|
||||||
|
|
||||||
#### Wireless
|
#### Wireless
|
||||||
@ -479,15 +551,6 @@ scrcpy --turn-screen-off --stay-awake
|
|||||||
scrcpy -Sw
|
scrcpy -Sw
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Renderizzare i fotogrammi scaduti
|
|
||||||
|
|
||||||
Per minimizzare la latenza _scrcpy_ renderizza sempre l'ultimo fotogramma decodificato disponibile in maniera predefinita e tralascia quelli precedenti.
|
|
||||||
|
|
||||||
Per forzare la renderizzazione di tutti i fotogrammi (a costo di una possibile latenza superiore), utilizzare:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --render-expired-frames
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Mostrare i tocchi
|
#### Mostrare i tocchi
|
||||||
|
|
||||||
@ -607,14 +670,14 @@ Non c'è alcuna risposta visiva, un log è stampato nella console.
|
|||||||
|
|
||||||
#### Trasferimento di file verso il dispositivo
|
#### Trasferimento di file verso il dispositivo
|
||||||
|
|
||||||
Per trasferire un file in `/sdcard/` del dispositivo trascina e rilascia un file (non APK) nella finestra di _scrcpy_.
|
Per trasferire un file in `/sdcard/Download` del dispositivo trascina e rilascia un file (non APK) nella finestra di _scrcpy_.
|
||||||
|
|
||||||
Non c'è alcuna risposta visiva, un log è stampato nella console.
|
Non c'è alcuna risposta visiva, un log è stampato nella console.
|
||||||
|
|
||||||
La cartella di destinazione può essere cambiata all'avvio:
|
La cartella di destinazione può essere cambiata all'avvio:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --push-target=/sdcard/Download/
|
scrcpy --push-target=/sdcard/Movies/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -653,10 +716,10 @@ _<kbd>[Super]</kbd> è il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kbd>._
|
|||||||
| Rotazione schermo a sinistra | <kbd>MOD</kbd>+<kbd>←</kbd> _(sinistra)_
|
| Rotazione schermo a sinistra | <kbd>MOD</kbd>+<kbd>←</kbd> _(sinistra)_
|
||||||
| Rotazione schermo a destra | <kbd>MOD</kbd>+<kbd>→</kbd> _(destra)_
|
| Rotazione schermo a destra | <kbd>MOD</kbd>+<kbd>→</kbd> _(destra)_
|
||||||
| Ridimensiona finestra a 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
| Ridimensiona finestra a 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
||||||
| Ridimensiona la finestra per rimuovere i bordi neri | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Doppio click¹_
|
| Ridimensiona la finestra per rimuovere i bordi neri | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Doppio click sinistro¹_
|
||||||
| Premi il tasto `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Click centrale_
|
| Premi il tasto `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Click centrale_
|
||||||
| Premi il tasto `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Click destro²_
|
| Premi il tasto `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Click destro²_
|
||||||
| Premi il tasto `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd>
|
| Premi il tasto `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4° click³_
|
||||||
| Premi il tasto `MENU` (sblocca lo schermo) | <kbd>MOD</kbd>+<kbd>m</kbd>
|
| Premi il tasto `MENU` (sblocca lo schermo) | <kbd>MOD</kbd>+<kbd>m</kbd>
|
||||||
| Premi il tasto `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(su)_
|
| Premi il tasto `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(su)_
|
||||||
| Premi il tasto `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(giù)_
|
| Premi il tasto `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(giù)_
|
||||||
@ -665,18 +728,26 @@ _<kbd>[Super]</kbd> è il pulsante <kbd>Windows</kbd> o <kbd>Cmd</kbd>._
|
|||||||
| Spegni lo schermo del dispositivo (continua a trasmettere) | <kbd>MOD</kbd>+<kbd>o</kbd>
|
| Spegni lo schermo del dispositivo (continua a trasmettere) | <kbd>MOD</kbd>+<kbd>o</kbd>
|
||||||
| Accendi lo schermo del dispositivo | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
| Accendi lo schermo del dispositivo | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
||||||
| Ruota lo schermo del dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
|
| Ruota lo schermo del dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
|
||||||
| Espandi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>n</kbd>
|
| Espandi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5° click³_
|
||||||
| Chiudi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
| Espandi il pannello delle impostazioni | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Doppio 5° click³_
|
||||||
| Copia negli appunti³ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
| Chiudi pannelli | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||||
| Taglia negli appunti³ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
| Copia negli appunti⁴ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||||
| Sincronizza gli appunti e incolla³ | <kbd>MOD</kbd>+<kbd>v</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>
|
| Inietta il testo degli appunti del computer | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
||||||
| Abilita/Disabilita il contatore FPS (su stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
| Abilita/Disabilita il contatore FPS (su stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||||
| Pizzica per zoomare | <kbd>Ctrl</kbd>+_click e trascina_
|
| Pizzica per zoomare | <kbd>Ctrl</kbd>+_click e trascina_
|
||||||
|
|
||||||
_¹Doppio click sui bordi neri per rimuoverli._
|
_¹Doppio click sui bordi neri per rimuoverli._
|
||||||
_²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._
|
_²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._
|
||||||
_³Solo in Android >= 7._
|
_³4° e 5° pulsante del mouse, se il tuo mouse ne dispone._
|
||||||
|
_⁴Solo in Android >= 7._
|
||||||
|
|
||||||
|
Le scorciatoie con pulsanti ripetuti sono eseguite rilasciando e premendo il pulsante una seconda volta. Per esempio, per eseguire "Espandi il pannello delle impostazioni":
|
||||||
|
|
||||||
|
1. Premi e tieni premuto <kbd>MOD</kbd>.
|
||||||
|
2. Poi premi due volte <kbd>n</kbd>.
|
||||||
|
3. Infine rilascia <kbd>MOD</kbd>.
|
||||||
|
|
||||||
Tutte le scorciatoie <kbd>Ctrl</kbd>+_tasto_ sono inoltrate al dispositivo, così sono gestite dall'applicazione attiva.
|
Tutte le scorciatoie <kbd>Ctrl</kbd>+_tasto_ sono inoltrate al dispositivo, così sono gestite dall'applicazione attiva.
|
||||||
|
|
||||||
|
74
README.md
74
README.md
@ -1,7 +1,5 @@
|
|||||||
# scrcpy (v1.19)
|
# scrcpy (v1.19)
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
[Read in another language](#translations)
|
[Read in another language](#translations)
|
||||||
|
|
||||||
This application provides display and control of Android devices connected on
|
This application provides display and control of Android devices connected on
|
||||||
@ -209,6 +207,29 @@ scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
|
|||||||
|
|
||||||
If `--max-size` is also specified, resizing is applied after cropping.
|
If `--max-size` is also specified, resizing is applied after cropping.
|
||||||
|
|
||||||
|
#### USB HID over AOAv2
|
||||||
|
|
||||||
|
Scrcpy can simulate a USB physical keyboard on Android to provide better input
|
||||||
|
experience, you need to connect your device via USB, not wireless.
|
||||||
|
|
||||||
|
However, due to some limitation of libusb and WinUSB driver, you cannot use HID
|
||||||
|
over AOAv2 on Windows.
|
||||||
|
|
||||||
|
Currently a USB serial number is needed to use HID over AOAv2.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --serial XXXXXXXXXXXXXXXX # don't use HID
|
||||||
|
scrcpy --serial XXXXXXXXXXXXXXXX --input-mode inject # don't use HID
|
||||||
|
scrcpy --serial XXXXXXXXXXXXXXXX --input-mode hid # try HID and exit if failed
|
||||||
|
```
|
||||||
|
|
||||||
|
Serial number can be found by `adb get-serialno`.
|
||||||
|
|
||||||
|
If you are a non-QWERTY keyboard user and using HID mode, please remember to set
|
||||||
|
correct physical keyboard layout manually in Android settings, because scrcpy
|
||||||
|
just forwards scancodes to Android device and Android system is responsible for
|
||||||
|
converting scancodes to correct keycode on Android device (your system does this
|
||||||
|
on your PC).
|
||||||
|
|
||||||
#### Lock video orientation
|
#### Lock video orientation
|
||||||
|
|
||||||
@ -326,7 +347,9 @@ For example, you could capture the video within [OBS].
|
|||||||
#### Buffering
|
#### Buffering
|
||||||
|
|
||||||
It is possible to add buffering. This increases latency but reduces jitter (see
|
It is possible to add buffering. This increases latency but reduces jitter (see
|
||||||
#2464).
|
[#2464]).
|
||||||
|
|
||||||
|
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
|
||||||
|
|
||||||
The option is available for display buffering:
|
The option is available for display buffering:
|
||||||
|
|
||||||
@ -673,39 +696,6 @@ content (if supported by the app) relative to the center of the screen.
|
|||||||
Concretely, scrcpy generates additional touch events from a "virtual finger" at
|
Concretely, scrcpy generates additional touch events from a "virtual finger" at
|
||||||
a location inverted through the center of the screen.
|
a location inverted through the center of the screen.
|
||||||
|
|
||||||
#### Physical keyboard simulation (HID)
|
|
||||||
|
|
||||||
By default, scrcpy uses Android key or text injection: it works everywhere, but
|
|
||||||
is limited to ASCII.
|
|
||||||
|
|
||||||
On Linux, scrcpy can simulate a USB physical keyboard on Android to provide a
|
|
||||||
better input experience (using [USB HID over AOAv2][hid-aoav2]): the virtual
|
|
||||||
keyboard is disabled and it works for all characters and IME.
|
|
||||||
|
|
||||||
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
|
|
||||||
|
|
||||||
However, it only works if the device is connected by USB, and is currently only
|
|
||||||
supported on Linux.
|
|
||||||
|
|
||||||
To enable this mode:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --hid-keyboard
|
|
||||||
scrcpy -K # short version
|
|
||||||
```
|
|
||||||
|
|
||||||
If it fails for some reason (for example because the device is not connected via
|
|
||||||
USB), it automatically fallbacks to the default mode (with a log in the
|
|
||||||
console). This allows to use the same command line options when connected over
|
|
||||||
USB and TCP/IP.
|
|
||||||
|
|
||||||
In this mode, raw key events (scancodes) are sent to the device, independently
|
|
||||||
of the host key mapping. Therefore, if your keyboard layout does not match, it
|
|
||||||
must be configured on the Android device, in Settings → System → Languages and
|
|
||||||
input → [Physical keyboard].
|
|
||||||
|
|
||||||
[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
|
|
||||||
|
|
||||||
|
|
||||||
#### Text injection preference
|
#### Text injection preference
|
||||||
|
|
||||||
@ -725,9 +715,6 @@ scrcpy --prefer-text
|
|||||||
|
|
||||||
(but this will break keyboard behavior in games)
|
(but this will break keyboard behavior in games)
|
||||||
|
|
||||||
This option has no effect on HID keyboard (all key events are sent as
|
|
||||||
scancodes in this mode).
|
|
||||||
|
|
||||||
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
||||||
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
||||||
|
|
||||||
@ -743,9 +730,6 @@ To avoid forwarding repeated key events:
|
|||||||
scrcpy --no-key-repeat
|
scrcpy --no-key-repeat
|
||||||
```
|
```
|
||||||
|
|
||||||
This option has no effect on HID keyboard (key repeat is handled by Android
|
|
||||||
directly in this mode).
|
|
||||||
|
|
||||||
|
|
||||||
#### Right-click and middle-click
|
#### Right-click and middle-click
|
||||||
|
|
||||||
@ -867,7 +851,7 @@ ADB=/path/to/adb scrcpy
|
|||||||
To override the path of the `scrcpy-server` file, configure its path in
|
To override the path of the `scrcpy-server` file, configure its path in
|
||||||
`SCRCPY_SERVER_PATH`.
|
`SCRCPY_SERVER_PATH`.
|
||||||
|
|
||||||
To override the icon, configure its path in `SCRCPY_ICON_PATH`.
|
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
|
||||||
|
|
||||||
|
|
||||||
## Why _scrcpy_?
|
## Why _scrcpy_?
|
||||||
@ -927,10 +911,10 @@ Read the [developers page].
|
|||||||
This README is available in other languages:
|
This README is available in other languages:
|
||||||
|
|
||||||
- [Indonesian (Indonesia, `id`) - v1.16](README.id.md)
|
- [Indonesian (Indonesia, `id`) - v1.16](README.id.md)
|
||||||
- [Italiano (Italiano, `it`) - v1.17](README.it.md)
|
- [Italiano (Italiano, `it`) - v1.19](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.19](README.pt-br.md)
|
||||||
- [Español (Spanish, `sp`) - v1.17](README.sp.md)
|
- [Español (Spanish, `sp`) - v1.17](README.sp.md)
|
||||||
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.17](README.zh-Hans.md)
|
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.17](README.zh-Hans.md)
|
||||||
- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md)
|
- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md)
|
||||||
|
172
README.pt-br.md
172
README.pt-br.md
@ -1,6 +1,6 @@
|
|||||||
_Apenas o [README](README.md) original é garantido estar atualizado._
|
_Apenas o [README](README.md) original é garantido estar atualizado._
|
||||||
|
|
||||||
# scrcpy (v1.17)
|
# scrcpy (v1.19)
|
||||||
|
|
||||||
Esta aplicação fornece exibição e controle de dispositivos Android conectados via
|
Esta aplicação fornece exibição e controle de dispositivos Android conectados via
|
||||||
USB (ou [via TCP/IP][article-tcpip]). Não requer nenhum acesso _root_.
|
USB (ou [via TCP/IP][article-tcpip]). Não requer nenhum acesso _root_.
|
||||||
@ -38,6 +38,18 @@ controlá-lo usando teclado e mouse.
|
|||||||
|
|
||||||
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
|
<a href="https://repology.org/project/scrcpy/versions"><img src="https://repology.org/badge/vertical-allrepos/scrcpy.svg" alt="Packaging status" align="right"></a>
|
||||||
|
|
||||||
|
### Sumário
|
||||||
|
|
||||||
|
- Linux: `apt install scrcpy`
|
||||||
|
- Windows: [baixar][direct-win64]
|
||||||
|
- macOS: `brew install scrcpy`
|
||||||
|
|
||||||
|
Compilar pelos arquivos fontes: [BUILD] ([processo simplificado][BUILD_simple])
|
||||||
|
|
||||||
|
[BUILD]: BUILD.md
|
||||||
|
[BUILD_simple]: BUILD.md#simple
|
||||||
|
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
No Debian (_testing_ e _sid_ por enquanto) e Ubuntu (20.04):
|
No Debian (_testing_ e _sid_ por enquanto) e Ubuntu (20.04):
|
||||||
@ -67,9 +79,7 @@ Para Gentoo, uma [Ebuild] está disponível: [`scrcpy/`][ebuild-link].
|
|||||||
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
|
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
|
||||||
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
|
[ebuild-link]: https://github.com/maggu2810/maggu2810-overlay/tree/master/app-mobilephone/scrcpy
|
||||||
|
|
||||||
Você também pode [compilar o app manualmente][BUILD] (não se preocupe, não é tão
|
Você também pode [compilar o app manualmente][BUILD] ([processo simplificado][BUILD_simple]).
|
||||||
difícil).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
@ -113,13 +123,18 @@ brew install scrcpy
|
|||||||
Você precisa do `adb`, acessível pelo seu `PATH`. Se você ainda não o tem:
|
Você precisa do `adb`, acessível pelo seu `PATH`. Se você ainda não o tem:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Está também disponivel em [MacPorts], que prepara o adb para você:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo port install scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
|
[MacPorts]: https://www.macports.org/
|
||||||
|
|
||||||
|
|
||||||
Você também pode [compilar o app manualmente][BUILD].
|
Você também pode [compilar o app manualmente][BUILD].
|
||||||
|
|
||||||
|
|
||||||
@ -195,10 +210,11 @@ Se `--max-size` também for especificado, o redimensionamento é aplicado após
|
|||||||
Para travar a orientação do espelhamento:
|
Para travar a orientação do espelhamento:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scrcpy --lock-video-orientation 0 # orientação natural
|
scrcpy --lock-video-orientation # orientação inicial (Atual)
|
||||||
scrcpy --lock-video-orientation 1 # 90° sentido anti-horário
|
scrcpy --lock-video-orientation=0 # orientação natural
|
||||||
scrcpy --lock-video-orientation 2 # 180°
|
scrcpy --lock-video-orientation=1 # 90° sentido anti-horário
|
||||||
scrcpy --lock-video-orientation 3 # 90° sentido horário
|
scrcpy --lock-video-orientation=2 # 180°
|
||||||
|
scrcpy --lock-video-orientation=3 # 90° sentido horário
|
||||||
```
|
```
|
||||||
|
|
||||||
Isso afeta a orientação de gravação.
|
Isso afeta a orientação de gravação.
|
||||||
@ -222,7 +238,9 @@ erro dará os encoders disponíveis:
|
|||||||
scrcpy --encoder _
|
scrcpy --encoder _
|
||||||
```
|
```
|
||||||
|
|
||||||
### Gravando
|
### Captura
|
||||||
|
|
||||||
|
#### Gravando
|
||||||
|
|
||||||
É possível gravar a tela enquanto ocorre o espelhamento:
|
É possível gravar a tela enquanto ocorre o espelhamento:
|
||||||
|
|
||||||
@ -246,6 +264,79 @@ pacotes][packet delay variation] não impacta o arquivo gravado.
|
|||||||
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
|
||||||
|
|
||||||
|
|
||||||
|
#### v4l2loopback
|
||||||
|
|
||||||
|
Em Linux, é possível enviar a transmissão do video para um disposiivo v4l2 loopback, assim
|
||||||
|
o dispositivo Android pode ser aberto como uma webcam por qualquer ferramneta capaz de v4l2
|
||||||
|
|
||||||
|
The module `v4l2loopback` must be installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install v4l2loopback-dkms
|
||||||
|
```
|
||||||
|
|
||||||
|
Para criar um dispositivo v4l2:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo modprobe v4l2loopback
|
||||||
|
```
|
||||||
|
|
||||||
|
Isso criara um novo dispositivo de vídeo em `/dev/videoN`, onde `N` é uma integer
|
||||||
|
(mais [opções](https://github.com/umlaeute/v4l2loopback#options) estão disponiveis
|
||||||
|
para criar varios dispositivos ou dispositivos com IDs específicas).
|
||||||
|
|
||||||
|
Para listar os dispositivos disponíveis:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# requer o pacote v4l-utils
|
||||||
|
v4l2-ctl --list-devices
|
||||||
|
|
||||||
|
# simples, mas pode ser suficiente
|
||||||
|
ls /dev/video*
|
||||||
|
```
|
||||||
|
|
||||||
|
Para iniciar o scrcpy usando o coletor v4l2 (sink):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --v4l2-sink=/dev/videoN
|
||||||
|
scrcpy --v4l2-sink=/dev/videoN --no-display # desativa a janela espelhada
|
||||||
|
scrcpy --v4l2-sink=/dev/videoN -N # versão curta
|
||||||
|
```
|
||||||
|
|
||||||
|
(troque `N` pelo ID do dipositivo, verifique com `ls /dev/video*`)
|
||||||
|
|
||||||
|
Uma vez ativado, você pode abrir suas trasmissões de videos com uma ferramenta capaz de v4l2:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ffplay -i /dev/videoN
|
||||||
|
vlc v4l2:///dev/videoN # VLC pode adicionar um pouco de atraso de buffering
|
||||||
|
```
|
||||||
|
|
||||||
|
Por exemplo, você pode capturar o video dentro do [OBS].
|
||||||
|
|
||||||
|
[OBS]: https://obsproject.com/
|
||||||
|
|
||||||
|
|
||||||
|
#### Buffering
|
||||||
|
|
||||||
|
É possivel adicionar buffering. Isso aumenta a latência, mas reduz a tenção (jitter) (veja
|
||||||
|
[#2464]).
|
||||||
|
|
||||||
|
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
|
||||||
|
|
||||||
|
A opção éta disponivel para buffering de exibição:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --display-buffer=50 # adiciona 50 ms de buffering para a exibição
|
||||||
|
```
|
||||||
|
|
||||||
|
e coletor V4L2:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --v4l2-buffer=500 # adiciona 500 ms de buffering para coletor V4L2
|
||||||
|
```
|
||||||
|
|
||||||
|
,
|
||||||
### Conexão
|
### Conexão
|
||||||
|
|
||||||
#### Sem fio
|
#### Sem fio
|
||||||
@ -488,18 +579,6 @@ scrcpy -Sw
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### Renderizar frames expirados
|
|
||||||
|
|
||||||
Por padrão, para minimizar a latência, _scrcpy_ sempre renderiza o último frame decodificado
|
|
||||||
disponível, e descarta o anterior.
|
|
||||||
|
|
||||||
Para forçar a renderização de todos os frames (com o custo de um possível aumento de
|
|
||||||
latência), use:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --render-expired-frames
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Mostrar toques
|
#### Mostrar toques
|
||||||
|
|
||||||
Para apresentações, pode ser útil mostrar toques físicos (no dispositivo
|
Para apresentações, pode ser útil mostrar toques físicos (no dispositivo
|
||||||
@ -647,7 +726,7 @@ Não existe feedback visual, um log é imprimido no console.
|
|||||||
|
|
||||||
#### Enviar arquivo para dispositivo
|
#### Enviar arquivo para dispositivo
|
||||||
|
|
||||||
Para enviar um arquivo para `/sdcard/` no dispositivo, arraste e solte um arquivo (não-APK) para a
|
Para enviar um arquivo para `/sdcard/Download/` no dispositivo, arraste e solte um arquivo (não-APK) para a
|
||||||
janela do _scrcpy_.
|
janela do _scrcpy_.
|
||||||
|
|
||||||
Não existe feedback visual, um log é imprimido no console.
|
Não existe feedback visual, um log é imprimido no console.
|
||||||
@ -694,12 +773,12 @@ _<kbd>[Super]</kbd> é tipicamente a tecla <kbd>Windows</kbd> ou <kbd>Cmd</kbd>.
|
|||||||
| Mudar modo de tela cheia | <kbd>MOD</kbd>+<kbd>f</kbd>
|
| Mudar modo de tela cheia | <kbd>MOD</kbd>+<kbd>f</kbd>
|
||||||
| Rotacionar display para esquerda | <kbd>MOD</kbd>+<kbd>←</kbd> _(esquerda)_
|
| Rotacionar display para esquerda | <kbd>MOD</kbd>+<kbd>←</kbd> _(esquerda)_
|
||||||
| Rotacionar display para direita | <kbd>MOD</kbd>+<kbd>→</kbd> _(direita)_
|
| Rotacionar display para direita | <kbd>MOD</kbd>+<kbd>→</kbd> _(direita)_
|
||||||
| Redimensionar janela para 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
| Redimensionar janela para 1:1 (pixel-perfeito) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
||||||
| Redimensionar janela para remover bordas pretas | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Clique-duplo¹_
|
| Redimensionar janela para remover bordas pretas | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Clique-duplo-esquerdo¹_
|
||||||
| Clicar em `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Clique-do-meio_
|
| Clicar em `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Clique-do-meio_
|
||||||
| Clicar em `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Clique-direito²_
|
| Clicar em `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Clique-direito²_
|
||||||
| Clicar em `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd>
|
| Clicar em `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _Clique-do-4.°³_
|
||||||
| Clicar em `MENU` (desbloquear tela | <kbd>MOD</kbd>+<kbd>m</kbd>
|
| Clicar em `MENU` (desbloquear tela) | <kbd>MOD</kbd>+<kbd>m</kbd>
|
||||||
| Clicar em `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(cima)_
|
| Clicar em `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(cima)_
|
||||||
| Clicar em `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(baixo)_
|
| Clicar em `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(baixo)_
|
||||||
| Clicar em `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
|
| Clicar em `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
|
||||||
@ -707,18 +786,27 @@ _<kbd>[Super]</kbd> é tipicamente a tecla <kbd>Windows</kbd> ou <kbd>Cmd</kbd>.
|
|||||||
| Desligar tela do dispositivo (continuar espelhando) | <kbd>MOD</kbd>+<kbd>o</kbd>
|
| Desligar tela do dispositivo (continuar espelhando) | <kbd>MOD</kbd>+<kbd>o</kbd>
|
||||||
| Ligar tela do dispositivo | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
| Ligar tela do dispositivo | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
||||||
| Rotacionar tela do dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
|
| Rotacionar tela do dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
|
||||||
| Expandir painel de notificação | <kbd>MOD</kbd>+<kbd>n</kbd>
|
| Expandir painel de notificação | <kbd>MOD</kbd>+<kbd>n</kbd> \| _Clique-do-5.°³_
|
||||||
| Colapsar painel de notificação | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
| Expandir painel de configurção | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Clique-duplo-do-5.°³_
|
||||||
| Copiar para área de transferência³ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
| Colapsar paineis | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||||
| Recortar para área de transferência³ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
| Copiar para área de transferência⁴ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||||
| Sincronizar áreas de transferência e colar³ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
| Recortar para área de transferência⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||||
|
| Sincronizar áreas de transferência e colar⁴ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||||
| Injetar texto da área de transferência do computador | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
| Injetar texto da área de transferência do computador | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
||||||
| Ativar/desativar contador de FPS (em stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
| Ativar/desativar contador de FPS (em stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||||
| Pinçar para dar zoom | <kbd>Ctrl</kbd>+_clicar-e-mover_
|
| Pinçar para dar zoom | <kbd>Ctrl</kbd>+_Clicar-e-mover_
|
||||||
|
|
||||||
_¹Clique-duplo em bordas pretas para removê-las._
|
_¹Clique-duplo-esquerdo na borda preta para remove-la._
|
||||||
_²Clique-direito liga a tela se ela estiver desligada, pressiona BACK caso contrário._
|
_²Clique-direito liga a tela caso esteja desligada, pressione BACK caso contrário._
|
||||||
_³Apenas em Android >= 7._
|
_³4.° and 5.° botões do mouse, caso o mouse possua._
|
||||||
|
_⁴Apenas em Android >= 7._
|
||||||
|
|
||||||
|
Atalhos com teclas reptidas são executados soltando e precionando a tecla
|
||||||
|
uma segunda vez. Por exemplo, para executar "Expandir painel de Configurção":
|
||||||
|
|
||||||
|
1. Mantenha pressionado <kbd>MOD</kbd>.
|
||||||
|
2. Depois click duas vezes <kbd>n</kbd>.
|
||||||
|
3. Finalmente, solte <kbd>MOD</kbd>.
|
||||||
|
|
||||||
Todos os atalhos <kbd>Ctrl</kbd>+_tecla_ são encaminhados para o dispositivo, para que eles sejam
|
Todos os atalhos <kbd>Ctrl</kbd>+_tecla_ são encaminhados para o dispositivo, para que eles sejam
|
||||||
tratados pela aplicação ativa.
|
tratados pela aplicação ativa.
|
||||||
@ -729,7 +817,9 @@ tratados pela aplicação ativa.
|
|||||||
Para usar um binário _adb_ específico, configure seu caminho na variável de ambiente
|
Para usar um binário _adb_ específico, configure seu caminho na variável de ambiente
|
||||||
`ADB`:
|
`ADB`:
|
||||||
|
|
||||||
|
```bash
|
||||||
ADB=/caminho/para/adb scrcpy
|
ADB=/caminho/para/adb scrcpy
|
||||||
|
```
|
||||||
|
|
||||||
Para sobrepor o caminho do arquivo `scrcpy-server`, configure seu caminho em
|
Para sobrepor o caminho do arquivo `scrcpy-server`, configure seu caminho em
|
||||||
`SCRCPY_SERVER_PATH`.
|
`SCRCPY_SERVER_PATH`.
|
||||||
@ -751,8 +841,6 @@ Um colega me desafiou a encontrar um nome tão impronunciável quanto [gnirehtet
|
|||||||
|
|
||||||
Veja [BUILD].
|
Veja [BUILD].
|
||||||
|
|
||||||
[BUILD]: BUILD.md
|
|
||||||
|
|
||||||
|
|
||||||
## Problemas comuns
|
## Problemas comuns
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ src = [
|
|||||||
'src/controller.c',
|
'src/controller.c',
|
||||||
'src/decoder.c',
|
'src/decoder.c',
|
||||||
'src/device_msg.c',
|
'src/device_msg.c',
|
||||||
'src/icon.c',
|
|
||||||
'src/file_handler.c',
|
'src/file_handler.c',
|
||||||
'src/fps_counter.c',
|
'src/fps_counter.c',
|
||||||
'src/frame_buffer.c',
|
'src/frame_buffer.c',
|
||||||
@ -16,13 +15,13 @@ src = [
|
|||||||
'src/keyboard_inject.c',
|
'src/keyboard_inject.c',
|
||||||
'src/mouse_inject.c',
|
'src/mouse_inject.c',
|
||||||
'src/opengl.c',
|
'src/opengl.c',
|
||||||
'src/options.c',
|
|
||||||
'src/receiver.c',
|
'src/receiver.c',
|
||||||
'src/recorder.c',
|
'src/recorder.c',
|
||||||
'src/scrcpy.c',
|
'src/scrcpy.c',
|
||||||
'src/screen.c',
|
'src/screen.c',
|
||||||
'src/server.c',
|
'src/server.c',
|
||||||
'src/stream.c',
|
'src/stream.c',
|
||||||
|
'src/tiny_xpm.c',
|
||||||
'src/video_buffer.c',
|
'src/video_buffer.c',
|
||||||
'src/util/log.c',
|
'src/util/log.c',
|
||||||
'src/util/net.c',
|
'src/util/net.c',
|
||||||
@ -166,9 +165,6 @@ executable('scrcpy', src,
|
|||||||
c_args: [])
|
c_args: [])
|
||||||
|
|
||||||
install_man('scrcpy.1')
|
install_man('scrcpy.1')
|
||||||
install_data('../data/icon.png',
|
|
||||||
rename: 'scrcpy.png',
|
|
||||||
install_dir: 'share/icons/hicolor/256x256/apps')
|
|
||||||
|
|
||||||
|
|
||||||
### TESTS
|
### TESTS
|
||||||
@ -185,7 +181,6 @@ if get_option('buildtype') == 'debug'
|
|||||||
['test_cli', [
|
['test_cli', [
|
||||||
'tests/test_cli.c',
|
'tests/test_cli.c',
|
||||||
'src/cli.c',
|
'src/cli.c',
|
||||||
'src/options.c',
|
|
||||||
'src/util/str_util.c',
|
'src/util/str_util.c',
|
||||||
]],
|
]],
|
||||||
['test_clock', [
|
['test_clock', [
|
||||||
|
12
app/scrcpy.1
12
app/scrcpy.1
@ -83,13 +83,23 @@ Start in fullscreen.
|
|||||||
Print this help.
|
Print this help.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-K, \-\-hid\-keyboard
|
.B \-K, \-\-keyboard\-hid
|
||||||
Simulate a physical keyboard by using HID over AOAv2.
|
Simulate a physical keyboard by using HID over AOAv2.
|
||||||
|
|
||||||
This provides a better experience for IME users, and allows to generate non-ASCII characters, contrary to the default injection method.
|
This provides a better experience for IME users, and allows to generate non-ASCII characters, contrary to the default injection method.
|
||||||
|
|
||||||
It may only work over USB, and is currently only supported on Linux.
|
It may only work over USB, and is currently only supported on Linux.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B \-i, \-\-input\-mode mode
|
||||||
|
Select input mode for keyboard events.
|
||||||
|
|
||||||
|
Possible values are "hid" and "inject".
|
||||||
|
|
||||||
|
"hid" uses Android's USB HID over AOAv2 feature to simulate physical keyboard's events, which provides better experience for IME users if supported by you device.
|
||||||
|
|
||||||
|
"inject" is the legacy scrcpy way by injecting keycode events on Android, works on most devices and is the default.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B \-\-legacy\-paste
|
.B \-\-legacy\-paste
|
||||||
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).
|
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
#define DEFAULT_TIMEOUT 1000
|
#define DEFAULT_TIMEOUT 1000
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_hid_event_log(const struct sc_hid_event *event) {
|
hid_event_log(const struct hid_event *event) {
|
||||||
// HID Event: [00] FF FF FF FF...
|
// HID Event: [00] FF FF FF FF...
|
||||||
assert(event->size);
|
assert(event->size);
|
||||||
unsigned buffer_size = event->size * 3 + 1;
|
unsigned buffer_size = event->size * 3 + 1;
|
||||||
@ -25,25 +25,16 @@ sc_hid_event_log(const struct sc_hid_event *event) {
|
|||||||
for (unsigned i = 0; i < event->size; ++i) {
|
for (unsigned i = 0; i < event->size; ++i) {
|
||||||
snprintf(buffer + i * 3, 4, " %02x", event->buffer[i]);
|
snprintf(buffer + i * 3, 4, " %02x", event->buffer[i]);
|
||||||
}
|
}
|
||||||
LOGV("HID Event: [%d]%s", event->accessory_id, buffer);
|
LOGV("HID Event: [%d]%s", event->from_accessory_id, buffer);
|
||||||
free(buffer);
|
free(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id,
|
hid_event_destroy(struct hid_event *event) {
|
||||||
unsigned char *buffer, uint16_t buffer_size) {
|
free(event->buffer);
|
||||||
hid_event->accessory_id = accessory_id;
|
|
||||||
hid_event->buffer = buffer;
|
|
||||||
hid_event->size = buffer_size;
|
|
||||||
hid_event->delay = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
sc_hid_event_destroy(struct sc_hid_event *hid_event) {
|
|
||||||
free(hid_event->buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
log_libusb_error(enum libusb_error errcode) {
|
log_libusb_error(enum libusb_error errcode) {
|
||||||
LOGW("libusb error: %s", libusb_strerror(errcode));
|
LOGW("libusb error: %s", libusb_strerror(errcode));
|
||||||
}
|
}
|
||||||
@ -82,7 +73,7 @@ accept_device(libusb_device *device, const char *serial) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static libusb_device *
|
static libusb_device *
|
||||||
sc_aoa_find_usb_device(const char *serial) {
|
aoa_find_usb_device(const char *serial) {
|
||||||
if (!serial) {
|
if (!serial) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@ -108,7 +99,7 @@ sc_aoa_find_usb_device(const char *serial) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
sc_aoa_open_usb_handle(libusb_device *device, libusb_device_handle **handle) {
|
aoa_open_usb_handle(libusb_device *device, libusb_device_handle **handle) {
|
||||||
int result = libusb_open(device, handle);
|
int result = libusb_open(device, handle);
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
log_libusb_error((enum libusb_error) result);
|
log_libusb_error((enum libusb_error) result);
|
||||||
@ -118,7 +109,7 @@ sc_aoa_open_usb_handle(libusb_device *device, libusb_device_handle **handle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_aoa_init(struct sc_aoa *aoa, const char *serial) {
|
aoa_init(struct aoa *aoa, const char *serial) {
|
||||||
cbuf_init(&aoa->queue);
|
cbuf_init(&aoa->queue);
|
||||||
|
|
||||||
if (!sc_mutex_init(&aoa->mutex)) {
|
if (!sc_mutex_init(&aoa->mutex)) {
|
||||||
@ -136,7 +127,7 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
aoa->usb_device = sc_aoa_find_usb_device(serial);
|
aoa->usb_device = aoa_find_usb_device(serial);
|
||||||
if (!aoa->usb_device) {
|
if (!aoa->usb_device) {
|
||||||
LOGW("USB device of serial %s not found", serial);
|
LOGW("USB device of serial %s not found", serial);
|
||||||
libusb_exit(aoa->usb_context);
|
libusb_exit(aoa->usb_context);
|
||||||
@ -145,7 +136,7 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sc_aoa_open_usb_handle(aoa->usb_device, &aoa->usb_handle) < 0) {
|
if (aoa_open_usb_handle(aoa->usb_device, &aoa->usb_handle) < 0) {
|
||||||
LOGW("Open USB handle failed");
|
LOGW("Open USB handle failed");
|
||||||
libusb_unref_device(aoa->usb_device);
|
libusb_unref_device(aoa->usb_device);
|
||||||
libusb_exit(aoa->usb_context);
|
libusb_exit(aoa->usb_context);
|
||||||
@ -160,11 +151,11 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_aoa_destroy(struct sc_aoa *aoa) {
|
aoa_destroy(struct aoa *aoa) {
|
||||||
// Destroy remaining events
|
// Destroy remaining events
|
||||||
struct sc_hid_event event;
|
struct hid_event event;
|
||||||
while (cbuf_take(&aoa->queue, &event)) {
|
while (cbuf_take(&aoa->queue, &event)) {
|
||||||
sc_hid_event_destroy(&event);
|
hid_event_destroy(&event);
|
||||||
}
|
}
|
||||||
|
|
||||||
libusb_close(aoa->usb_handle);
|
libusb_close(aoa->usb_handle);
|
||||||
@ -175,7 +166,7 @@ sc_aoa_destroy(struct sc_aoa *aoa) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id,
|
aoa_register_hid(struct aoa *aoa, uint16_t accessory_id,
|
||||||
uint16_t report_desc_size) {
|
uint16_t report_desc_size) {
|
||||||
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
||||||
uint8_t request = ACCESSORY_REGISTER_HID;
|
uint8_t request = ACCESSORY_REGISTER_HID;
|
||||||
@ -198,7 +189,7 @@ sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id,
|
aoa_set_hid_report_desc(struct aoa *aoa, uint16_t accessory_id,
|
||||||
const unsigned char *report_desc,
|
const unsigned char *report_desc,
|
||||||
uint16_t report_desc_size) {
|
uint16_t report_desc_size) {
|
||||||
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
||||||
@ -234,17 +225,17 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
|
aoa_setup_hid(struct aoa *aoa, uint16_t accessory_id,
|
||||||
const unsigned char *report_desc, uint16_t report_desc_size) {
|
const unsigned char *report_desc, uint16_t report_desc_size) {
|
||||||
bool ok = sc_aoa_register_hid(aoa, accessory_id, report_desc_size);
|
bool ok = aoa_register_hid(aoa, accessory_id, report_desc_size);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_aoa_set_hid_report_desc(aoa, accessory_id, report_desc,
|
ok = aoa_set_hid_report_desc(aoa, accessory_id, report_desc,
|
||||||
report_desc_size);
|
report_desc_size);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
if (!sc_aoa_unregister_hid(aoa, accessory_id)) {
|
if (!aoa_unregister_hid(aoa, accessory_id)) {
|
||||||
LOGW("Could not unregister HID");
|
LOGW("Could not unregister HID");
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -254,13 +245,13 @@ sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
|
aoa_send_hid_event(struct aoa *aoa, const struct hid_event *event) {
|
||||||
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
||||||
uint8_t request = ACCESSORY_SEND_HID_EVENT;
|
uint8_t request = ACCESSORY_SEND_HID_EVENT;
|
||||||
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
|
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
|
||||||
// value (arg0): accessory assigned ID for the HID device
|
// value (arg0): accessory assigned ID for the HID device
|
||||||
// index (arg1): 0 (unused)
|
// index (arg1): 0 (unused)
|
||||||
uint16_t value = event->accessory_id;
|
uint16_t value = event->from_accessory_id;
|
||||||
uint16_t index = 0;
|
uint16_t index = 0;
|
||||||
unsigned char *buffer = event->buffer;
|
unsigned char *buffer = event->buffer;
|
||||||
uint16_t length = event->size;
|
uint16_t length = event->size;
|
||||||
@ -276,7 +267,7 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) {
|
aoa_unregister_hid(struct aoa *aoa, const uint16_t accessory_id) {
|
||||||
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
||||||
uint8_t request = ACCESSORY_UNREGISTER_HID;
|
uint8_t request = ACCESSORY_UNREGISTER_HID;
|
||||||
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
|
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
|
||||||
@ -298,11 +289,8 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_aoa_push_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
|
aoa_push_hid_event(struct aoa *aoa, const struct hid_event *event) {
|
||||||
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
hid_event_log(event);
|
||||||
sc_hid_event_log(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_mutex_lock(&aoa->mutex);
|
sc_mutex_lock(&aoa->mutex);
|
||||||
bool was_empty = cbuf_is_empty(&aoa->queue);
|
bool was_empty = cbuf_is_empty(&aoa->queue);
|
||||||
bool res = cbuf_push(&aoa->queue, *event);
|
bool res = cbuf_push(&aoa->queue, *event);
|
||||||
@ -315,7 +303,7 @@ sc_aoa_push_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
|
|||||||
|
|
||||||
static int
|
static int
|
||||||
run_aoa_thread(void *data) {
|
run_aoa_thread(void *data) {
|
||||||
struct sc_aoa *aoa = data;
|
struct aoa *aoa = data;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
sc_mutex_lock(&aoa->mutex);
|
sc_mutex_lock(&aoa->mutex);
|
||||||
@ -327,30 +315,14 @@ run_aoa_thread(void *data) {
|
|||||||
sc_mutex_unlock(&aoa->mutex);
|
sc_mutex_unlock(&aoa->mutex);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
struct sc_hid_event event;
|
struct hid_event event;
|
||||||
bool non_empty = cbuf_take(&aoa->queue, &event);
|
bool non_empty = cbuf_take(&aoa->queue, &event);
|
||||||
assert(non_empty);
|
assert(non_empty);
|
||||||
(void) non_empty;
|
(void) non_empty;
|
||||||
|
|
||||||
assert(event.delay >= 0);
|
|
||||||
if (event.delay) {
|
|
||||||
// Wait during the specified delay before injecting the HID event
|
|
||||||
sc_tick deadline = sc_tick_now() + event.delay;
|
|
||||||
bool timed_out = false;
|
|
||||||
while (!aoa->stopped && !timed_out) {
|
|
||||||
timed_out = !sc_cond_timedwait(&aoa->event_cond, &aoa->mutex,
|
|
||||||
deadline);
|
|
||||||
}
|
|
||||||
if (aoa->stopped) {
|
|
||||||
sc_mutex_unlock(&aoa->mutex);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_mutex_unlock(&aoa->mutex);
|
sc_mutex_unlock(&aoa->mutex);
|
||||||
|
|
||||||
bool ok = sc_aoa_send_hid_event(aoa, &event);
|
bool ok = aoa_send_hid_event(aoa, &event);
|
||||||
sc_hid_event_destroy(&event);
|
hid_event_destroy(&event);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGW("Could not send HID event to USB device");
|
LOGW("Could not send HID event to USB device");
|
||||||
}
|
}
|
||||||
@ -359,7 +331,7 @@ run_aoa_thread(void *data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_aoa_start(struct sc_aoa *aoa) {
|
aoa_start(struct aoa *aoa) {
|
||||||
LOGD("Starting AOA thread");
|
LOGD("Starting AOA thread");
|
||||||
|
|
||||||
bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "aoa_thread", aoa);
|
bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "aoa_thread", aoa);
|
||||||
@ -372,7 +344,7 @@ sc_aoa_start(struct sc_aoa *aoa) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_aoa_stop(struct sc_aoa *aoa) {
|
aoa_stop(struct aoa *aoa) {
|
||||||
sc_mutex_lock(&aoa->mutex);
|
sc_mutex_lock(&aoa->mutex);
|
||||||
aoa->stopped = true;
|
aoa->stopped = true;
|
||||||
sc_cond_signal(&aoa->event_cond);
|
sc_cond_signal(&aoa->event_cond);
|
||||||
@ -380,6 +352,6 @@ sc_aoa_stop(struct sc_aoa *aoa) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_aoa_join(struct sc_aoa *aoa) {
|
aoa_join(struct aoa *aoa) {
|
||||||
sc_thread_join(&aoa->thread, NULL);
|
sc_thread_join(&aoa->thread, NULL);
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,24 @@
|
|||||||
#ifndef SC_AOA_HID_H
|
#ifndef AOA_HID_H
|
||||||
#define SC_AOA_HID_H
|
#define AOA_HID_H
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include <libusb-1.0/libusb.h>
|
#include <libusb-1.0/libusb.h>
|
||||||
|
|
||||||
|
#include "scrcpy.h"
|
||||||
#include "util/cbuf.h"
|
#include "util/cbuf.h"
|
||||||
#include "util/thread.h"
|
#include "util/thread.h"
|
||||||
#include "util/tick.h"
|
|
||||||
|
|
||||||
struct sc_hid_event {
|
struct hid_event {
|
||||||
uint16_t accessory_id;
|
uint16_t from_accessory_id;
|
||||||
unsigned char *buffer;
|
unsigned char *buffer;
|
||||||
uint16_t size;
|
uint16_t size;
|
||||||
sc_tick delay;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Takes ownership of buffer
|
struct hid_event_queue CBUF(struct hid_event, 64);
|
||||||
void
|
|
||||||
sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id,
|
|
||||||
unsigned char *buffer, uint16_t buffer_size);
|
|
||||||
|
|
||||||
void
|
struct aoa {
|
||||||
sc_hid_event_destroy(struct sc_hid_event *hid_event);
|
|
||||||
|
|
||||||
struct sc_hid_event_queue CBUF(struct sc_hid_event, 64);
|
|
||||||
|
|
||||||
struct sc_aoa {
|
|
||||||
libusb_context *usb_context;
|
libusb_context *usb_context;
|
||||||
libusb_device *usb_device;
|
libusb_device *usb_device;
|
||||||
libusb_device_handle *usb_handle;
|
libusb_device_handle *usb_handle;
|
||||||
@ -35,32 +26,32 @@ struct sc_aoa {
|
|||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
sc_cond event_cond;
|
sc_cond event_cond;
|
||||||
bool stopped;
|
bool stopped;
|
||||||
struct sc_hid_event_queue queue;
|
struct hid_event_queue queue;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_aoa_init(struct sc_aoa *aoa, const char *serial);
|
aoa_init(struct aoa *aoa, const char *serial);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_aoa_destroy(struct sc_aoa *aoa);
|
aoa_destroy(struct aoa *aoa);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_aoa_start(struct sc_aoa *aoa);
|
aoa_start(struct aoa *aoa);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_aoa_stop(struct sc_aoa *aoa);
|
aoa_stop(struct aoa *aoa);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_aoa_join(struct sc_aoa *aoa);
|
aoa_join(struct aoa *aoa);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
|
aoa_setup_hid(struct aoa *aoa, uint16_t accessory_id,
|
||||||
const unsigned char *report_desc, uint16_t report_desc_size);
|
const unsigned char *report_desc, uint16_t report_desc_size);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id);
|
aoa_unregister_hid(struct aoa *aoa, uint16_t accessory_id);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_aoa_push_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event);
|
aoa_push_hid_event(struct aoa *aoa, const struct hid_event *event);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "options.h"
|
#include "scrcpy.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/str_util.h"
|
#include "util/str_util.h"
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ scrcpy_print_usage(const char *arg0) {
|
|||||||
" -f, --fullscreen\n"
|
" -f, --fullscreen\n"
|
||||||
" Start in fullscreen.\n"
|
" Start in fullscreen.\n"
|
||||||
"\n"
|
"\n"
|
||||||
" -K, --hid-keyboard\n"
|
" -K, --keyboard-hid\n"
|
||||||
" Simulate a physical keyboard by using HID over AOAv2.\n"
|
" Simulate a physical keyboard by using HID over AOAv2.\n"
|
||||||
" It provides a better experience for IME users, and allows to\n"
|
" It provides a better experience for IME users, and allows to\n"
|
||||||
" generate non-ASCII characters, contrary to the default\n"
|
" generate non-ASCII characters, contrary to the default\n"
|
||||||
@ -746,7 +746,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
|
|||||||
OPT_FORWARD_ALL_CLICKS},
|
OPT_FORWARD_ALL_CLICKS},
|
||||||
{"fullscreen", no_argument, NULL, 'f'},
|
{"fullscreen", no_argument, NULL, 'f'},
|
||||||
{"help", no_argument, NULL, 'h'},
|
{"help", no_argument, NULL, 'h'},
|
||||||
{"hid-keyboard", no_argument, NULL, 'K'},
|
{"keyboard-hid", no_argument, NULL, 'K'},
|
||||||
{"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE},
|
{"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE},
|
||||||
{"lock-video-orientation", optional_argument, NULL,
|
{"lock-video-orientation", optional_argument, NULL,
|
||||||
OPT_LOCK_VIDEO_ORIENTATION},
|
OPT_LOCK_VIDEO_ORIENTATION},
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "options.h"
|
#include "scrcpy.h"
|
||||||
|
|
||||||
struct scrcpy_cli_args {
|
struct scrcpy_cli_args {
|
||||||
struct scrcpy_options opts;
|
struct scrcpy_options opts;
|
||||||
|
@ -26,7 +26,7 @@ struct sc_clock_point {
|
|||||||
* array.
|
* array.
|
||||||
*
|
*
|
||||||
* To estimate the slope, it splits the last SC_CLOCK_RANGE points into two
|
* To estimate the slope, it splits the last SC_CLOCK_RANGE points into two
|
||||||
* sets of SC_CLOCK_RANGE/2 points, and computes their centroid ("average
|
* sets of SC_CLOCK_RANGE/2 points, and compute their centroid ("average
|
||||||
* point"). The slope of the estimated affine function is that of the line
|
* point"). The slope of the estimated affine function is that of the line
|
||||||
* passing through these two points.
|
* passing through these two points.
|
||||||
*
|
*
|
||||||
|
@ -56,7 +56,7 @@ static const char *const screen_power_mode_labels[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
write_position(uint8_t *buf, const struct sc_position *position) {
|
write_position(uint8_t *buf, const struct position *position) {
|
||||||
buffer_write32be(&buf[0], position->point.x);
|
buffer_write32be(&buf[0], position->point.x);
|
||||||
buffer_write32be(&buf[4], position->point.y);
|
buffer_write32be(&buf[4], position->point.y);
|
||||||
buffer_write16be(&buf[8], position->screen_size.width);
|
buffer_write16be(&buf[8], position->screen_size.width);
|
||||||
|
@ -57,11 +57,11 @@ struct control_msg {
|
|||||||
enum android_motionevent_action action;
|
enum android_motionevent_action action;
|
||||||
enum android_motionevent_buttons buttons;
|
enum android_motionevent_buttons buttons;
|
||||||
uint64_t pointer_id;
|
uint64_t pointer_id;
|
||||||
struct sc_position position;
|
struct position position;
|
||||||
float pressure;
|
float pressure;
|
||||||
} inject_touch_event;
|
} inject_touch_event;
|
||||||
struct {
|
struct {
|
||||||
struct sc_position position;
|
struct position position;
|
||||||
int32_t hscroll;
|
int32_t hscroll;
|
||||||
int32_t vscroll;
|
int32_t vscroll;
|
||||||
} inject_scroll_event;
|
} inject_scroll_event;
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
controller_init(struct controller *controller, sc_socket control_socket) {
|
controller_init(struct controller *controller, socket_t control_socket) {
|
||||||
cbuf_init(&controller->queue);
|
cbuf_init(&controller->queue);
|
||||||
|
|
||||||
bool ok = receiver_init(&controller->receiver, control_socket);
|
bool ok = receiver_init(&controller->receiver, control_socket);
|
||||||
@ -63,14 +63,14 @@ controller_push_msg(struct controller *controller,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
process_msg(struct controller *controller, const struct control_msg *msg) {
|
process_msg(struct controller *controller,
|
||||||
|
const struct control_msg *msg) {
|
||||||
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
|
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
|
||||||
size_t length = control_msg_serialize(msg, serialized_msg);
|
size_t length = control_msg_serialize(msg, serialized_msg);
|
||||||
if (!length) {
|
if (!length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ssize_t w =
|
ssize_t w = net_send_all(controller->control_socket, serialized_msg, length);
|
||||||
net_send_all(controller->control_socket, serialized_msg, length);
|
|
||||||
return (size_t) w == length;
|
return (size_t) w == length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
struct control_msg_queue CBUF(struct control_msg, 64);
|
struct control_msg_queue CBUF(struct control_msg, 64);
|
||||||
|
|
||||||
struct controller {
|
struct controller {
|
||||||
sc_socket control_socket;
|
socket_t control_socket;
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
sc_cond msg_cond;
|
sc_cond msg_cond;
|
||||||
@ -24,7 +24,7 @@ struct controller {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
controller_init(struct controller *controller, sc_socket control_socket);
|
controller_init(struct controller *controller, socket_t control_socket);
|
||||||
|
|
||||||
void
|
void
|
||||||
controller_destroy(struct controller *controller);
|
controller_destroy(struct controller *controller);
|
||||||
|
@ -3,22 +3,22 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
struct sc_size {
|
struct size {
|
||||||
uint16_t width;
|
uint16_t width;
|
||||||
uint16_t height;
|
uint16_t height;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_point {
|
struct point {
|
||||||
int32_t x;
|
int32_t x;
|
||||||
int32_t y;
|
int32_t y;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_position {
|
struct position {
|
||||||
// The video screen size may be different from the real device screen size,
|
// The video screen size may be different from the real device screen size,
|
||||||
// so store to which size the absolute position apply, to scale it
|
// so store to which size the absolute position apply, to scale it
|
||||||
// accordingly.
|
// accordingly.
|
||||||
struct sc_size screen_size;
|
struct size screen_size;
|
||||||
struct sc_point point;
|
struct point point;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,5 +1,2 @@
|
|||||||
#define EVENT_NEW_FRAME SDL_USEREVENT
|
#define EVENT_NEW_FRAME SDL_USEREVENT
|
||||||
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
|
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
|
||||||
#define EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
|
|
||||||
#define EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
|
|
||||||
#define EVENT_SERVER_DISCONNECTED (SDL_USEREVENT + 4)
|
|
||||||
|
@ -6,19 +6,20 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
/** Downcast key processor to hid_keyboard */
|
/** Downcast key processor to hid_keyboard */
|
||||||
#define DOWNCAST(KP) container_of(KP, struct sc_hid_keyboard, key_processor)
|
#define DOWNCAST(KP) \
|
||||||
|
container_of(KP, struct hid_keyboard, key_processor)
|
||||||
|
|
||||||
#define HID_KEYBOARD_ACCESSORY_ID 1
|
#define HID_KEYBOARD_ACCESSORY_ID 1
|
||||||
|
|
||||||
#define HID_MODIFIER_NONE 0x00
|
#define HID_KEYBOARD_MODIFIER_NONE 0x00
|
||||||
#define HID_MODIFIER_LEFT_CONTROL (1 << 0)
|
#define HID_KEYBOARD_MODIFIER_LEFT_CONTROL (1 << 0)
|
||||||
#define HID_MODIFIER_LEFT_SHIFT (1 << 1)
|
#define HID_KEYBOARD_MODIFIER_LEFT_SHIFT (1 << 1)
|
||||||
#define HID_MODIFIER_LEFT_ALT (1 << 2)
|
#define HID_KEYBOARD_MODIFIER_LEFT_ALT (1 << 2)
|
||||||
#define HID_MODIFIER_LEFT_GUI (1 << 3)
|
#define HID_KEYBOARD_MODIFIER_LEFT_GUI (1 << 3)
|
||||||
#define HID_MODIFIER_RIGHT_CONTROL (1 << 4)
|
#define HID_KEYBOARD_MODIFIER_RIGHT_CONTROL (1 << 4)
|
||||||
#define HID_MODIFIER_RIGHT_SHIFT (1 << 5)
|
#define HID_KEYBOARD_MODIFIER_RIGHT_SHIFT (1 << 5)
|
||||||
#define HID_MODIFIER_RIGHT_ALT (1 << 6)
|
#define HID_KEYBOARD_MODIFIER_RIGHT_ALT (1 << 6)
|
||||||
#define HID_MODIFIER_RIGHT_GUI (1 << 7)
|
#define HID_KEYBOARD_MODIFIER_RIGHT_GUI (1 << 7)
|
||||||
|
|
||||||
#define HID_KEYBOARD_INDEX_MODIFIER 0
|
#define HID_KEYBOARD_INDEX_MODIFIER 0
|
||||||
#define HID_KEYBOARD_INDEX_KEYS 2
|
#define HID_KEYBOARD_INDEX_KEYS 2
|
||||||
@ -29,8 +30,11 @@
|
|||||||
#define HID_KEYBOARD_MAX_KEYS 6
|
#define HID_KEYBOARD_MAX_KEYS 6
|
||||||
#define HID_KEYBOARD_EVENT_SIZE (2 + HID_KEYBOARD_MAX_KEYS)
|
#define HID_KEYBOARD_EVENT_SIZE (2 + HID_KEYBOARD_MAX_KEYS)
|
||||||
|
|
||||||
#define HID_RESERVED 0x00
|
#define HID_KEYBOARD_RESERVED 0x00
|
||||||
#define HID_ERROR_ROLL_OVER 0x01
|
#define HID_KEYBOARD_ERROR_ROLL_OVER 0x01
|
||||||
|
|
||||||
|
#define SC_SCANCODE_CAPSLOCK 57
|
||||||
|
#define SC_SCANCODE_NUMLOCK 83
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For HID over AOAv2, only report descriptor is needed.
|
* For HID over AOAv2, only report descriptor is needed.
|
||||||
@ -110,11 +114,11 @@ static const unsigned char keyboard_report_desc[] = {
|
|||||||
// Usage Minimum (0)
|
// Usage Minimum (0)
|
||||||
0x19, 0x00,
|
0x19, 0x00,
|
||||||
// Usage Maximum (101)
|
// Usage Maximum (101)
|
||||||
0x29, SC_HID_KEYBOARD_KEYS - 1,
|
0x29, HID_KEYBOARD_KEYS - 1,
|
||||||
// Logical Minimum (0)
|
// Logical Minimum (0)
|
||||||
0x15, 0x00,
|
0x15, 0x00,
|
||||||
// Logical Maximum(101)
|
// Logical Maximum(101)
|
||||||
0x25, SC_HID_KEYBOARD_KEYS - 1,
|
0x25, HID_KEYBOARD_KEYS - 1,
|
||||||
// Report Size (8)
|
// Report Size (8)
|
||||||
0x75, 0x08,
|
0x75, 0x08,
|
||||||
// Report Count (6)
|
// Report Count (6)
|
||||||
@ -126,49 +130,129 @@ static const unsigned char keyboard_report_desc[] = {
|
|||||||
0xC0
|
0xC0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static unsigned char *
|
||||||
|
create_hid_keyboard_event(void) {
|
||||||
|
unsigned char *buffer = malloc(HID_KEYBOARD_EVENT_SIZE);
|
||||||
|
if (!buffer) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[HID_KEYBOARD_INDEX_MODIFIER] = HID_KEYBOARD_MODIFIER_NONE;
|
||||||
|
buffer[1] = HID_KEYBOARD_RESERVED;
|
||||||
|
memset(&buffer[HID_KEYBOARD_INDEX_KEYS], 0, HID_KEYBOARD_MAX_KEYS);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
static unsigned char
|
static unsigned char
|
||||||
sdl_keymod_to_hid_modifiers(SDL_Keymod mod) {
|
sdl_keymod_to_hid_modifiers(SDL_Keymod mod) {
|
||||||
unsigned char modifiers = HID_MODIFIER_NONE;
|
unsigned char modifiers = HID_KEYBOARD_MODIFIER_NONE;
|
||||||
if (mod & KMOD_LCTRL) {
|
if (mod & KMOD_LCTRL) {
|
||||||
modifiers |= HID_MODIFIER_LEFT_CONTROL;
|
modifiers |= HID_KEYBOARD_MODIFIER_LEFT_CONTROL;
|
||||||
}
|
}
|
||||||
if (mod & KMOD_LSHIFT) {
|
if (mod & KMOD_LSHIFT) {
|
||||||
modifiers |= HID_MODIFIER_LEFT_SHIFT;
|
modifiers |= HID_KEYBOARD_MODIFIER_LEFT_SHIFT;
|
||||||
}
|
}
|
||||||
if (mod & KMOD_LALT) {
|
if (mod & KMOD_LALT) {
|
||||||
modifiers |= HID_MODIFIER_LEFT_ALT;
|
modifiers |= HID_KEYBOARD_MODIFIER_LEFT_ALT;
|
||||||
}
|
}
|
||||||
if (mod & KMOD_LGUI) {
|
if (mod & KMOD_LGUI) {
|
||||||
modifiers |= HID_MODIFIER_LEFT_GUI;
|
modifiers |= HID_KEYBOARD_MODIFIER_LEFT_GUI;
|
||||||
}
|
}
|
||||||
if (mod & KMOD_RCTRL) {
|
if (mod & KMOD_RCTRL) {
|
||||||
modifiers |= HID_MODIFIER_RIGHT_CONTROL;
|
modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_CONTROL;
|
||||||
}
|
}
|
||||||
if (mod & KMOD_RSHIFT) {
|
if (mod & KMOD_RSHIFT) {
|
||||||
modifiers |= HID_MODIFIER_RIGHT_SHIFT;
|
modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_SHIFT;
|
||||||
}
|
}
|
||||||
if (mod & KMOD_RALT) {
|
if (mod & KMOD_RALT) {
|
||||||
modifiers |= HID_MODIFIER_RIGHT_ALT;
|
modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_ALT;
|
||||||
}
|
}
|
||||||
if (mod & KMOD_RGUI) {
|
if (mod & KMOD_RGUI) {
|
||||||
modifiers |= HID_MODIFIER_RIGHT_GUI;
|
modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_GUI;
|
||||||
}
|
}
|
||||||
return modifiers;
|
return modifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
|
send_mod_lock_state(struct hid_keyboard *kb, unsigned lock_mod) {
|
||||||
unsigned char *buffer = malloc(HID_KEYBOARD_EVENT_SIZE);
|
assert(!(lock_mod & ~SC_MOD_MASK));
|
||||||
if (!buffer) {
|
if (!lock_mod) {
|
||||||
return false;
|
// Nothing to do
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer[HID_KEYBOARD_INDEX_MODIFIER] = HID_MODIFIER_NONE;
|
struct hid_event hid_event;
|
||||||
buffer[1] = HID_RESERVED;
|
hid_event.from_accessory_id = HID_KEYBOARD_ACCESSORY_ID;
|
||||||
memset(&buffer[HID_KEYBOARD_INDEX_KEYS], 0, HID_KEYBOARD_MAX_KEYS);
|
hid_event.buffer = create_hid_keyboard_event();
|
||||||
|
if (!hid_event.buffer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
hid_event.size = HID_KEYBOARD_EVENT_SIZE;
|
||||||
|
|
||||||
|
unsigned i = 0;
|
||||||
|
if (lock_mod & SC_MOD_CAPSLOCK) {
|
||||||
|
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
if (lock_mod & SC_MOD_NUMLOCK) {
|
||||||
|
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for (int i = 0; i < HID_KEYBOARD_EVENT_SIZE; ++i)
|
||||||
|
// printf("%02x ", hid_event->buffer[i]);
|
||||||
|
// printf("\n");
|
||||||
|
|
||||||
|
if (!aoa_push_hid_event(kb->aoa, &hid_event)) {
|
||||||
|
LOGW("Could request HID event");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
convert_hid_keyboard_event(struct hid_keyboard *kb, struct hid_event *hid_event,
|
||||||
|
const SDL_KeyboardEvent *event) {
|
||||||
|
hid_event->buffer = create_hid_keyboard_event();
|
||||||
|
if (!hid_event->buffer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
hid_event->size = HID_KEYBOARD_EVENT_SIZE;
|
||||||
|
|
||||||
|
unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->keysym.mod);
|
||||||
|
|
||||||
|
SDL_Scancode scancode = event->keysym.scancode;
|
||||||
|
assert(scancode >= 0);
|
||||||
|
if (scancode < HID_KEYBOARD_KEYS) {
|
||||||
|
// Pressed is true and released is false
|
||||||
|
kb->keys[scancode] = (event->type == SDL_KEYDOWN);
|
||||||
|
LOGV("keys[%02x] = %s", scancode,
|
||||||
|
kb->keys[scancode] ? "true" : "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
hid_event->buffer[HID_KEYBOARD_INDEX_MODIFIER] = modifiers;
|
||||||
|
|
||||||
|
// Re-calculate pressed keys every time
|
||||||
|
int keys_pressed_count = 0;
|
||||||
|
for (int i = 0; i < HID_KEYBOARD_KEYS; ++i) {
|
||||||
|
if (kb->keys[i]) {
|
||||||
|
// USB HID protocol says that if keys exceeds report count, a
|
||||||
|
// phantom state should be reported
|
||||||
|
if (keys_pressed_count >= HID_KEYBOARD_MAX_KEYS) {
|
||||||
|
// Pantom state:
|
||||||
|
// - Modifiers
|
||||||
|
// - Reserved
|
||||||
|
// - ErrorRollOver * HID_KEYBOARD_MAX_KEYS
|
||||||
|
memset(&hid_event->buffer[HID_KEYBOARD_INDEX_KEYS],
|
||||||
|
HID_KEYBOARD_ERROR_ROLL_OVER, HID_KEYBOARD_MAX_KEYS);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hid_event->buffer[HID_KEYBOARD_INDEX_KEYS + keys_pressed_count] = i;
|
||||||
|
++keys_pressed_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sc_hid_event_init(hid_event, HID_KEYBOARD_ACCESSORY_ID, buffer,
|
|
||||||
HID_KEYBOARD_EVENT_SIZE);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,104 +262,30 @@ scancode_is_modifier(SDL_Scancode scancode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
|
hid_keyboard_convert_event(struct hid_keyboard *kb, struct hid_event *hid_event,
|
||||||
struct sc_hid_event *hid_event,
|
|
||||||
const SDL_KeyboardEvent *event) {
|
const SDL_KeyboardEvent *event) {
|
||||||
|
LOGV(
|
||||||
|
"Type: %s, Repeat: %s, Modifiers: %02x, Key: %02x",
|
||||||
|
event->type == SDL_KEYDOWN ? "down" : "up",
|
||||||
|
event->repeat != 0 ? "true" : "false",
|
||||||
|
sdl_keymod_to_hid_modifiers(event->keysym.mod),
|
||||||
|
event->keysym.scancode
|
||||||
|
);
|
||||||
|
|
||||||
|
hid_event->from_accessory_id = HID_KEYBOARD_ACCESSORY_ID;
|
||||||
|
|
||||||
SDL_Scancode scancode = event->keysym.scancode;
|
SDL_Scancode scancode = event->keysym.scancode;
|
||||||
assert(scancode >= 0);
|
assert(scancode >= 0);
|
||||||
|
|
||||||
// SDL also generates events when only modifiers are pressed, we cannot
|
// SDL also generates events when only modifiers are pressed, we cannot
|
||||||
// ignore them totally, for example press 'a' first then press 'Control',
|
// ignore them totally, for example press 'a' first then press 'Control',
|
||||||
// if we ignore 'Control' event, only 'a' is sent.
|
// if we ignore 'Control' event, only 'a' is sent.
|
||||||
if (scancode >= SC_HID_KEYBOARD_KEYS && !scancode_is_modifier(scancode)) {
|
if (scancode < HID_KEYBOARD_KEYS || scancode_is_modifier(scancode)) {
|
||||||
// Scancode to ignore
|
return convert_hid_keyboard_event(kb, hid_event, event);
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_hid_keyboard_event_init(hid_event)) {
|
|
||||||
LOGW("Could not initialize HID keyboard event");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->keysym.mod);
|
|
||||||
|
|
||||||
if (scancode < SC_HID_KEYBOARD_KEYS) {
|
|
||||||
// Pressed is true and released is false
|
|
||||||
kb->keys[scancode] = (event->type == SDL_KEYDOWN);
|
|
||||||
LOGV("keys[%02x] = %s", scancode,
|
|
||||||
kb->keys[scancode] ? "true" : "false");
|
|
||||||
}
|
|
||||||
|
|
||||||
hid_event->buffer[HID_KEYBOARD_INDEX_MODIFIER] = modifiers;
|
|
||||||
|
|
||||||
unsigned char *keys_buffer = &hid_event->buffer[HID_KEYBOARD_INDEX_KEYS];
|
|
||||||
// Re-calculate pressed keys every time
|
|
||||||
int keys_pressed_count = 0;
|
|
||||||
for (int i = 0; i < SC_HID_KEYBOARD_KEYS; ++i) {
|
|
||||||
if (kb->keys[i]) {
|
|
||||||
// USB HID protocol says that if keys exceeds report count, a
|
|
||||||
// phantom state should be reported
|
|
||||||
if (keys_pressed_count >= HID_KEYBOARD_MAX_KEYS) {
|
|
||||||
// Pantom state:
|
|
||||||
// - Modifiers
|
|
||||||
// - Reserved
|
|
||||||
// - ErrorRollOver * HID_MAX_KEYS
|
|
||||||
memset(keys_buffer, HID_ERROR_ROLL_OVER, HID_KEYBOARD_MAX_KEYS);
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
keys_buffer[keys_pressed_count] = i;
|
|
||||||
++keys_pressed_count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
end:
|
|
||||||
LOGV("hid keyboard: key %-4s scancode=%02x (%u) mod=%02x",
|
|
||||||
event->type == SDL_KEYDOWN ? "down" : "up", event->keysym.scancode,
|
|
||||||
event->keysym.scancode, modifiers);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static bool
|
|
||||||
push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) {
|
|
||||||
bool capslock = sdl_mod & KMOD_CAPS;
|
|
||||||
bool numlock = sdl_mod & KMOD_NUM;
|
|
||||||
if (!capslock && !numlock) {
|
|
||||||
// Nothing to do
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
|
||||||
if (!sc_hid_keyboard_event_init(&hid_event)) {
|
|
||||||
LOGW("Could not initialize HID keyboard event");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define SC_SCANCODE_CAPSLOCK SDL_SCANCODE_CAPSLOCK
|
|
||||||
#define SC_SCANCODE_NUMLOCK SDL_SCANCODE_NUMLOCKCLEAR
|
|
||||||
unsigned i = 0;
|
|
||||||
if (capslock) {
|
|
||||||
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
if (numlock) {
|
|
||||||
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
|
|
||||||
sc_hid_event_destroy(&hid_event);
|
|
||||||
LOGW("Could request HID event");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGD("HID keyboard state synchronized");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sc_key_processor_process_key(struct sc_key_processor *kp,
|
sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||||
const SDL_KeyboardEvent *event) {
|
const SDL_KeyboardEvent *event) {
|
||||||
@ -285,33 +295,12 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_hid_keyboard *kb = DOWNCAST(kp);
|
struct hid_keyboard *kb = DOWNCAST(kp);
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
struct hid_event hid_event;
|
||||||
// Not all keys are supported, just ignore unsupported keys
|
// Not all keys are supported, just ignore unsupported keys
|
||||||
if (convert_hid_keyboard_event(kb, &hid_event, event)) {
|
if (hid_keyboard_convert_event(kb, &hid_event, event)) {
|
||||||
if (!kb->mod_lock_synchronized) {
|
if (!aoa_push_hid_event(kb->aoa, &hid_event)) {
|
||||||
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
|
|
||||||
// keyboard state
|
|
||||||
if (push_mod_lock_state(kb, event->keysym.mod)) {
|
|
||||||
kb->mod_lock_synchronized = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Keycode keycode = event->keysym.sym;
|
|
||||||
bool down = event->type == SDL_KEYDOWN;
|
|
||||||
bool ctrl = event->keysym.mod & KMOD_CTRL;
|
|
||||||
bool shift = event->keysym.mod & KMOD_SHIFT;
|
|
||||||
if (ctrl && !shift && keycode == SDLK_v && down) {
|
|
||||||
// Ctrl+v is pressed, so clipboard synchronization has been
|
|
||||||
// requested. Wait a bit so that the clipboard is set before
|
|
||||||
// injecting Ctrl+v via HID, otherwise it would paste the old
|
|
||||||
// clipboard content.
|
|
||||||
hid_event.delay = SC_TICK_FROM_MS(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
|
|
||||||
sc_hid_event_destroy(&hid_event);
|
|
||||||
LOGW("Could request HID event");
|
LOGW("Could request HID event");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -327,21 +316,21 @@ sc_key_processor_process_text(struct sc_key_processor *kp,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
|
hid_keyboard_init(struct hid_keyboard *kb, struct aoa *aoa, unsigned lock_mod) {
|
||||||
kb->aoa = aoa;
|
kb->aoa = aoa;
|
||||||
|
|
||||||
bool ok = sc_aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID,
|
// FIXME In practice, sending CAPS_LOCK immediately after fails with a Pipe
|
||||||
|
// error but we must know immediately if this fails or not
|
||||||
|
bool ok = aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID,
|
||||||
keyboard_report_desc,
|
keyboard_report_desc,
|
||||||
ARRAY_LEN(keyboard_report_desc));
|
ARRAY_LEN(keyboard_report_desc));
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGW("Register HID keyboard failed");
|
LOGW("Register HID for keyboard failed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset all states
|
// Reset all states
|
||||||
memset(kb->keys, false, SC_HID_KEYBOARD_KEYS);
|
memset(kb->keys, false, HID_KEYBOARD_KEYS);
|
||||||
|
|
||||||
kb->mod_lock_synchronized = false;
|
|
||||||
|
|
||||||
static const struct sc_key_processor_ops ops = {
|
static const struct sc_key_processor_ops ops = {
|
||||||
.process_key = sc_key_processor_process_key,
|
.process_key = sc_key_processor_process_key,
|
||||||
@ -350,14 +339,18 @@ sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
|
|||||||
|
|
||||||
kb->key_processor.ops = &ops;
|
kb->key_processor.ops = &ops;
|
||||||
|
|
||||||
|
// FIXME to avoid pipe error
|
||||||
|
usleep(100000);
|
||||||
|
send_mod_lock_state(kb, lock_mod);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb) {
|
hid_keyboard_destroy(struct hid_keyboard *kb) {
|
||||||
// Unregister HID keyboard so the soft keyboard shows again on Android
|
// Unregister HID keyboard so the soft keyboard shows again on Android
|
||||||
bool ok = sc_aoa_unregister_hid(kb->aoa, HID_KEYBOARD_ACCESSORY_ID);
|
bool ok = aoa_unregister_hid(kb->aoa, HID_KEYBOARD_ACCESSORY_ID);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
LOGW("Could not unregister HID keyboard");
|
LOGW("Could not unregister HID");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
#ifndef SC_HID_KEYBOARD_H
|
#ifndef HID_KEYBOARD_H
|
||||||
#define SC_HID_KEYBOARD_H
|
#define HID_KEYBOARD_H
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include "aoa_hid.h"
|
#include "aoa_hid.h"
|
||||||
#include "trait/key_processor.h"
|
#include "trait/key_processor.h"
|
||||||
|
|
||||||
@ -12,7 +14,11 @@
|
|||||||
// Maybe SDL_Keycode is used by most people, but SDL_Scancode is taken from USB
|
// Maybe SDL_Keycode is used by most people, but SDL_Scancode is taken from USB
|
||||||
// HID protocol.
|
// HID protocol.
|
||||||
// 0x65 is Application, typically AT-101 Keyboard ends here.
|
// 0x65 is Application, typically AT-101 Keyboard ends here.
|
||||||
#define SC_HID_KEYBOARD_KEYS 0x66
|
#define HID_KEYBOARD_KEYS 0x66
|
||||||
|
|
||||||
|
#define SC_MOD_MASK 0x3
|
||||||
|
#define SC_MOD_CAPSLOCK 0x1
|
||||||
|
#define SC_MOD_NUMLOCK 0x2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HID keyboard events are sequence-based, every time keyboard state changes
|
* HID keyboard events are sequence-based, every time keyboard state changes
|
||||||
@ -26,19 +32,17 @@
|
|||||||
* phantom state. Don't forget that modifiers should be updated too, even for
|
* phantom state. Don't forget that modifiers should be updated too, even for
|
||||||
* phantom state.
|
* phantom state.
|
||||||
*/
|
*/
|
||||||
struct sc_hid_keyboard {
|
struct hid_keyboard {
|
||||||
struct sc_key_processor key_processor; // key processor trait
|
struct sc_key_processor key_processor; // key processor trait
|
||||||
|
|
||||||
struct sc_aoa *aoa;
|
struct aoa *aoa;
|
||||||
bool keys[SC_HID_KEYBOARD_KEYS];
|
bool keys[HID_KEYBOARD_KEYS];
|
||||||
|
|
||||||
bool mod_lock_synchronized;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa);
|
hid_keyboard_init(struct hid_keyboard *kb, struct aoa *aoa, unsigned lock_mod);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb);
|
hid_keyboard_destroy(struct hid_keyboard *kb);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
282
app/src/icon.c
282
app/src/icon.c
@ -1,282 +0,0 @@
|
|||||||
#include "icon.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <libavformat/avformat.h>
|
|
||||||
#include <libavutil/pixdesc.h>
|
|
||||||
#include <libavutil/pixfmt.h>
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "compat.h"
|
|
||||||
#include "util/log.h"
|
|
||||||
#include "util/process.h"
|
|
||||||
#include "util/str_util.h"
|
|
||||||
|
|
||||||
#define SCRCPY_PORTABLE_ICON_FILENAME "icon.png"
|
|
||||||
#define SCRCPY_DEFAULT_ICON_PATH \
|
|
||||||
PREFIX "/share/icons/hicolor/256x256/apps/scrcpy.png"
|
|
||||||
|
|
||||||
static char *
|
|
||||||
get_icon_path(void) {
|
|
||||||
#ifdef __WINDOWS__
|
|
||||||
const wchar_t *icon_path_env = _wgetenv(L"SCRCPY_ICON_PATH");
|
|
||||||
#else
|
|
||||||
const char *icon_path_env = getenv("SCRCPY_ICON_PATH");
|
|
||||||
#endif
|
|
||||||
if (icon_path_env) {
|
|
||||||
// if the envvar is set, use it
|
|
||||||
#ifdef __WINDOWS__
|
|
||||||
char *icon_path = utf8_from_wide_char(icon_path_env);
|
|
||||||
#else
|
|
||||||
char *icon_path = strdup(icon_path_env);
|
|
||||||
#endif
|
|
||||||
if (!icon_path) {
|
|
||||||
LOGE("Could not allocate memory");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
LOGD("Using SCRCPY_ICON_PATH: %s", icon_path);
|
|
||||||
return icon_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef PORTABLE
|
|
||||||
LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH);
|
|
||||||
char *icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH);
|
|
||||||
if (!icon_path) {
|
|
||||||
LOGE("Could not allocate memory");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
char *icon_path = get_local_file_path(SCRCPY_PORTABLE_ICON_FILENAME);
|
|
||||||
if (!icon_path) {
|
|
||||||
LOGE("Could not get icon path");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
LOGD("Using icon (portable): %s", icon_path);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return icon_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
static AVFrame *
|
|
||||||
decode_image(const char *path) {
|
|
||||||
AVFrame *result = NULL;
|
|
||||||
|
|
||||||
AVFormatContext *ctx = avformat_alloc_context();
|
|
||||||
if (!ctx) {
|
|
||||||
LOGE("Could not allocate image decoder context");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (avformat_open_input(&ctx, path, NULL, NULL) < 0) {
|
|
||||||
LOGE("Could not open image codec: %s", path);
|
|
||||||
goto free_ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (avformat_find_stream_info(ctx, NULL) < 0) {
|
|
||||||
LOGE("Could not find image stream info");
|
|
||||||
goto close_input;
|
|
||||||
}
|
|
||||||
|
|
||||||
int stream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
|
|
||||||
if (stream < 0 ) {
|
|
||||||
LOGE("Could not find best image stream");
|
|
||||||
goto close_input;
|
|
||||||
}
|
|
||||||
|
|
||||||
AVCodecParameters *params = ctx->streams[stream]->codecpar;
|
|
||||||
|
|
||||||
AVCodec *codec = avcodec_find_decoder(params->codec_id);
|
|
||||||
if (!codec) {
|
|
||||||
LOGE("Could not find image decoder");
|
|
||||||
goto close_input;
|
|
||||||
}
|
|
||||||
|
|
||||||
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
|
|
||||||
if (!codec_ctx) {
|
|
||||||
LOGE("Could not allocate codec context");
|
|
||||||
goto close_input;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (avcodec_parameters_to_context(codec_ctx, params) < 0) {
|
|
||||||
LOGE("Could not fill codec context");
|
|
||||||
goto free_codec_ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
|
|
||||||
LOGE("Could not open image codec");
|
|
||||||
goto free_codec_ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
AVFrame *frame = av_frame_alloc();
|
|
||||||
if (!frame) {
|
|
||||||
LOGE("Could not allocate frame");
|
|
||||||
goto close_codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
AVPacket *packet = av_packet_alloc();
|
|
||||||
if (!packet) {
|
|
||||||
LOGE("Could not allocate packet");
|
|
||||||
av_frame_free(&frame);
|
|
||||||
goto close_codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (av_read_frame(ctx, packet) < 0) {
|
|
||||||
LOGE("Could not read frame");
|
|
||||||
av_packet_free(&packet);
|
|
||||||
av_frame_free(&frame);
|
|
||||||
goto close_codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ret;
|
|
||||||
if ((ret = avcodec_send_packet(codec_ctx, packet)) < 0) {
|
|
||||||
LOGE("Could not send icon packet: %d", ret);
|
|
||||||
av_packet_free(&packet);
|
|
||||||
av_frame_free(&frame);
|
|
||||||
goto close_codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((ret = avcodec_receive_frame(codec_ctx, frame)) != 0) {
|
|
||||||
LOGE("Could not receive icon frame: %d", ret);
|
|
||||||
av_packet_free(&packet);
|
|
||||||
av_frame_free(&frame);
|
|
||||||
goto close_codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
av_packet_free(&packet);
|
|
||||||
|
|
||||||
result = frame;
|
|
||||||
|
|
||||||
close_codec:
|
|
||||||
avcodec_close(codec_ctx);
|
|
||||||
free_codec_ctx:
|
|
||||||
avcodec_free_context(&codec_ctx);
|
|
||||||
close_input:
|
|
||||||
avformat_close_input(&ctx);
|
|
||||||
free_ctx:
|
|
||||||
avformat_free_context(ctx);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static SDL_PixelFormatEnum
|
|
||||||
to_sdl_pixel_format(enum AVPixelFormat fmt) {
|
|
||||||
switch (fmt) {
|
|
||||||
case AV_PIX_FMT_RGB24: return SDL_PIXELFORMAT_RGB24;
|
|
||||||
case AV_PIX_FMT_BGR24: return SDL_PIXELFORMAT_BGR24;
|
|
||||||
case AV_PIX_FMT_ARGB: return SDL_PIXELFORMAT_ARGB32;
|
|
||||||
case AV_PIX_FMT_RGBA: return SDL_PIXELFORMAT_RGBA32;
|
|
||||||
case AV_PIX_FMT_ABGR: return SDL_PIXELFORMAT_ABGR32;
|
|
||||||
case AV_PIX_FMT_BGRA: return SDL_PIXELFORMAT_BGRA32;
|
|
||||||
case AV_PIX_FMT_RGB565BE: return SDL_PIXELFORMAT_RGB565;
|
|
||||||
case AV_PIX_FMT_RGB555BE: return SDL_PIXELFORMAT_RGB555;
|
|
||||||
case AV_PIX_FMT_BGR565BE: return SDL_PIXELFORMAT_BGR565;
|
|
||||||
case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_BGR555;
|
|
||||||
case AV_PIX_FMT_RGB444BE: return SDL_PIXELFORMAT_RGB444;
|
|
||||||
case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_BGR444;
|
|
||||||
case AV_PIX_FMT_PAL8: return SDL_PIXELFORMAT_INDEX8;
|
|
||||||
default: return SDL_PIXELFORMAT_UNKNOWN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static SDL_Surface *
|
|
||||||
load_from_path(const char *path) {
|
|
||||||
AVFrame *frame = decode_image(path);
|
|
||||||
if (!frame) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
|
|
||||||
if (!desc) {
|
|
||||||
LOGE("Could not get icon format descriptor");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_packed = !(desc->flags & AV_PIX_FMT_FLAG_PLANAR);
|
|
||||||
if (!is_packed) {
|
|
||||||
LOGE("Could not load non-packed icon");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_PixelFormatEnum format = to_sdl_pixel_format(frame->format);
|
|
||||||
if (format == SDL_PIXELFORMAT_UNKNOWN) {
|
|
||||||
LOGE("Unsupported icon pixel format: %s (%d)", desc->name,
|
|
||||||
frame->format);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
int bits_per_pixel = av_get_bits_per_pixel(desc);
|
|
||||||
SDL_Surface *surface =
|
|
||||||
SDL_CreateRGBSurfaceWithFormatFrom(frame->data[0],
|
|
||||||
frame->width, frame->height,
|
|
||||||
bits_per_pixel,
|
|
||||||
frame->linesize[0],
|
|
||||||
format);
|
|
||||||
|
|
||||||
if (!surface) {
|
|
||||||
LOGE("Could not create icon surface");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frame->format == AV_PIX_FMT_PAL8) {
|
|
||||||
// Initialize the SDL palette
|
|
||||||
uint8_t *data = frame->data[1];
|
|
||||||
SDL_Color colors[256];
|
|
||||||
for (int i = 0; i < 256; ++i) {
|
|
||||||
SDL_Color *color = &colors[i];
|
|
||||||
|
|
||||||
// The palette is transported in AVFrame.data[1], is 1024 bytes
|
|
||||||
// long (256 4-byte entries) and is formatted the same as in
|
|
||||||
// AV_PIX_FMT_RGB32 described above (i.e., it is also
|
|
||||||
// endian-specific).
|
|
||||||
// <https://ffmpeg.org/doxygen/4.1/pixfmt_8h.html#a9a8e335cf3be472042bc9f0cf80cd4c5>
|
|
||||||
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
|
||||||
color->a = data[i * 4];
|
|
||||||
color->r = data[i * 4 + 1];
|
|
||||||
color->g = data[i * 4 + 2];
|
|
||||||
color->b = data[i * 4 + 3];
|
|
||||||
#else
|
|
||||||
color->a = data[i * 4 + 3];
|
|
||||||
color->r = data[i * 4 + 2];
|
|
||||||
color->g = data[i * 4 + 1];
|
|
||||||
color->b = data[i * 4];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Palette *palette = surface->format->palette;
|
|
||||||
assert(palette);
|
|
||||||
int ret = SDL_SetPaletteColors(palette, colors, 0, 256);
|
|
||||||
if (ret) {
|
|
||||||
LOGE("Could not set palette colors");
|
|
||||||
SDL_FreeSurface(surface);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
surface->userdata = frame; // frame owns the data
|
|
||||||
|
|
||||||
return surface;
|
|
||||||
|
|
||||||
error:
|
|
||||||
av_frame_free(&frame);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Surface *
|
|
||||||
scrcpy_icon_load() {
|
|
||||||
char *icon_path = get_icon_path();
|
|
||||||
if (!icon_path) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Surface *icon = load_from_path(icon_path);
|
|
||||||
free(icon_path);
|
|
||||||
return icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
scrcpy_icon_destroy(SDL_Surface *icon) {
|
|
||||||
AVFrame *frame = icon->userdata;
|
|
||||||
assert(frame);
|
|
||||||
av_frame_free(&frame);
|
|
||||||
SDL_FreeSurface(icon);
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
#ifndef ICON_H
|
|
||||||
#define ICON_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <SDL2/SDL.h>
|
|
||||||
#include <libavformat/avformat.h>
|
|
||||||
|
|
||||||
SDL_Surface *
|
|
||||||
scrcpy_icon_load(void);
|
|
||||||
|
|
||||||
void
|
|
||||||
scrcpy_icon_destroy(SDL_Surface *icon);
|
|
||||||
|
|
||||||
#endif
|
|
53
app/src/icon.xpm
Normal file
53
app/src/icon.xpm
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/* XPM */
|
||||||
|
static char * icon_xpm[] = {
|
||||||
|
"48 48 2 1",
|
||||||
|
" c None",
|
||||||
|
". c #96C13E",
|
||||||
|
" .. .. ",
|
||||||
|
" ... ... ",
|
||||||
|
" ... ...... ... ",
|
||||||
|
" ................ ",
|
||||||
|
" .............. ",
|
||||||
|
" ................ ",
|
||||||
|
" .................. ",
|
||||||
|
" .................... ",
|
||||||
|
" ..... ........ ..... ",
|
||||||
|
" ..... ........ ..... ",
|
||||||
|
" ...................... ",
|
||||||
|
" ........................ ",
|
||||||
|
" ........................ ",
|
||||||
|
" ........................ ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" .... ........................ .... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" ...... ........................ ...... ",
|
||||||
|
" .... ........................ .... ",
|
||||||
|
" ........................ ",
|
||||||
|
" ...................... ",
|
||||||
|
" ...... ...... ",
|
||||||
|
" ...... ...... ",
|
||||||
|
" ...... ...... ",
|
||||||
|
" ...... ...... ",
|
||||||
|
" ...... ...... ",
|
||||||
|
" ...... ...... ",
|
||||||
|
" ...... ...... ",
|
||||||
|
" ...... ...... ",
|
||||||
|
" ...... ...... ",
|
||||||
|
" .... .... "};
|
@ -332,7 +332,7 @@ input_manager_process_text_input(struct input_manager *im,
|
|||||||
static bool
|
static bool
|
||||||
simulate_virtual_finger(struct input_manager *im,
|
simulate_virtual_finger(struct input_manager *im,
|
||||||
enum android_motionevent_action action,
|
enum android_motionevent_action action,
|
||||||
struct sc_point point) {
|
struct point point) {
|
||||||
bool up = action == AMOTION_EVENT_ACTION_UP;
|
bool up = action == AMOTION_EVENT_ACTION_UP;
|
||||||
|
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
@ -352,8 +352,8 @@ simulate_virtual_finger(struct input_manager *im,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct sc_point
|
static struct point
|
||||||
inverse_point(struct sc_point point, struct sc_size size) {
|
inverse_point(struct point point, struct size size) {
|
||||||
point.x = size.width - point.x;
|
point.x = size.width - point.x;
|
||||||
point.y = size.height - point.y;
|
point.y = size.height - point.y;
|
||||||
return point;
|
return point;
|
||||||
@ -376,6 +376,8 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
|
|
||||||
bool smod = is_shortcut_mod(im, mod);
|
bool smod = is_shortcut_mod(im, mod);
|
||||||
|
|
||||||
|
LOGD("=== %x", (int) mod);
|
||||||
|
|
||||||
if (down && !repeat) {
|
if (down && !repeat) {
|
||||||
if (keycode == im->last_keycode && mod == im->last_mod) {
|
if (keycode == im->last_keycode && mod == im->last_mod) {
|
||||||
++im->key_repeat;
|
++im->key_repeat;
|
||||||
@ -545,10 +547,10 @@ input_manager_process_mouse_motion(struct input_manager *im,
|
|||||||
im->mp->ops->process_mouse_motion(im->mp, event);
|
im->mp->ops->process_mouse_motion(im->mp, event);
|
||||||
|
|
||||||
if (im->vfinger_down) {
|
if (im->vfinger_down) {
|
||||||
struct sc_point mouse =
|
struct point mouse =
|
||||||
screen_convert_window_to_frame_coords(im->screen, event->x,
|
screen_convert_window_to_frame_coords(im->screen, event->x,
|
||||||
event->y);
|
event->y);
|
||||||
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
|
struct point vfinger = inverse_point(mouse, im->screen->frame_size);
|
||||||
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
|
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -630,10 +632,10 @@ input_manager_process_mouse_button(struct input_manager *im,
|
|||||||
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
|
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
|
||||||
if ((down && !im->vfinger_down && CTRL_PRESSED)
|
if ((down && !im->vfinger_down && CTRL_PRESSED)
|
||||||
|| (!down && im->vfinger_down)) {
|
|| (!down && im->vfinger_down)) {
|
||||||
struct sc_point mouse =
|
struct point mouse =
|
||||||
screen_convert_window_to_frame_coords(im->screen, event->x,
|
screen_convert_window_to_frame_coords(im->screen, event->x,
|
||||||
event->y);
|
event->y);
|
||||||
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
|
struct point vfinger = inverse_point(mouse, im->screen->frame_size);
|
||||||
enum android_motionevent_action action = down
|
enum android_motionevent_action action = down
|
||||||
? AMOTION_EVENT_ACTION_DOWN
|
? AMOTION_EVENT_ACTION_DOWN
|
||||||
: AMOTION_EVENT_ACTION_UP;
|
: AMOTION_EVENT_ACTION_UP;
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "fps_counter.h"
|
#include "fps_counter.h"
|
||||||
#include "options.h"
|
#include "scrcpy.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "trait/key_processor.h"
|
#include "trait/key_processor.h"
|
||||||
#include "trait/mouse_processor.h"
|
#include "trait/mouse_processor.h"
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "options.h"
|
#include "scrcpy.h"
|
||||||
#include "trait/key_processor.h"
|
#include "trait/key_processor.h"
|
||||||
|
|
||||||
struct sc_keyboard_inject {
|
struct sc_keyboard_inject {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#include "scrcpy.h"
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@ -11,8 +13,6 @@
|
|||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include "cli.h"
|
#include "cli.h"
|
||||||
#include "options.h"
|
|
||||||
#include "scrcpy.h"
|
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -48,7 +48,7 @@ main(int argc, char *argv[]) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct scrcpy_cli_args args = {
|
struct scrcpy_cli_args args = {
|
||||||
.opts = scrcpy_options_default,
|
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||||
.help = false,
|
.help = false,
|
||||||
.version = false,
|
.version = false,
|
||||||
};
|
};
|
||||||
|
@ -125,7 +125,7 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
|
|||||||
int mouse_y;
|
int mouse_y;
|
||||||
SDL_GetMouseState(&mouse_x, &mouse_y);
|
SDL_GetMouseState(&mouse_x, &mouse_y);
|
||||||
|
|
||||||
struct sc_position position = {
|
struct position position = {
|
||||||
.screen_size = screen->frame_size,
|
.screen_size = screen->frame_size,
|
||||||
.point = screen_convert_window_to_frame_coords(screen,
|
.point = screen_convert_window_to_frame_coords(screen,
|
||||||
mouse_x, mouse_y),
|
mouse_x, mouse_y),
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
|
#include "scrcpy.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "trait/mouse_processor.h"
|
#include "trait/mouse_processor.h"
|
||||||
|
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
#include "options.h"
|
|
||||||
|
|
||||||
const struct scrcpy_options scrcpy_options_default = {
|
|
||||||
.serial = NULL,
|
|
||||||
.crop = NULL,
|
|
||||||
.record_filename = NULL,
|
|
||||||
.window_title = NULL,
|
|
||||||
.push_target = NULL,
|
|
||||||
.render_driver = NULL,
|
|
||||||
.codec_options = NULL,
|
|
||||||
.encoder_name = NULL,
|
|
||||||
#ifdef HAVE_V4L2
|
|
||||||
.v4l2_device = NULL,
|
|
||||||
#endif
|
|
||||||
.log_level = SC_LOG_LEVEL_INFO,
|
|
||||||
.record_format = SC_RECORD_FORMAT_AUTO,
|
|
||||||
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT,
|
|
||||||
.port_range = {
|
|
||||||
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
|
|
||||||
.last = DEFAULT_LOCAL_PORT_RANGE_LAST,
|
|
||||||
},
|
|
||||||
.shortcut_mods = {
|
|
||||||
.data = {SC_MOD_LALT, SC_MOD_LSUPER},
|
|
||||||
.count = 2,
|
|
||||||
},
|
|
||||||
.max_size = 0,
|
|
||||||
.bit_rate = DEFAULT_BIT_RATE,
|
|
||||||
.max_fps = 0,
|
|
||||||
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
|
|
||||||
.rotation = 0,
|
|
||||||
.window_x = SC_WINDOW_POSITION_UNDEFINED,
|
|
||||||
.window_y = SC_WINDOW_POSITION_UNDEFINED,
|
|
||||||
.window_width = 0,
|
|
||||||
.window_height = 0,
|
|
||||||
.display_id = 0,
|
|
||||||
.display_buffer = 0,
|
|
||||||
.v4l2_buffer = 0,
|
|
||||||
.show_touches = false,
|
|
||||||
.fullscreen = false,
|
|
||||||
.always_on_top = false,
|
|
||||||
.control = true,
|
|
||||||
.display = true,
|
|
||||||
.turn_screen_off = false,
|
|
||||||
.prefer_text = false,
|
|
||||||
.window_borderless = false,
|
|
||||||
.mipmaps = true,
|
|
||||||
.stay_awake = false,
|
|
||||||
.force_adb_forward = false,
|
|
||||||
.disable_screensaver = false,
|
|
||||||
.forward_key_repeat = true,
|
|
||||||
.forward_all_clicks = false,
|
|
||||||
.legacy_paste = false,
|
|
||||||
.power_off_on_close = false,
|
|
||||||
};
|
|
@ -1,113 +0,0 @@
|
|||||||
#ifndef SCRCPY_OPTIONS_H
|
|
||||||
#define SCRCPY_OPTIONS_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include "util/tick.h"
|
|
||||||
|
|
||||||
enum sc_log_level {
|
|
||||||
SC_LOG_LEVEL_VERBOSE,
|
|
||||||
SC_LOG_LEVEL_DEBUG,
|
|
||||||
SC_LOG_LEVEL_INFO,
|
|
||||||
SC_LOG_LEVEL_WARN,
|
|
||||||
SC_LOG_LEVEL_ERROR,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum sc_record_format {
|
|
||||||
SC_RECORD_FORMAT_AUTO,
|
|
||||||
SC_RECORD_FORMAT_MP4,
|
|
||||||
SC_RECORD_FORMAT_MKV,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum sc_lock_video_orientation {
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
|
|
||||||
// lock the current orientation when scrcpy starts
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_1,
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_2,
|
|
||||||
SC_LOCK_VIDEO_ORIENTATION_3,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum sc_keyboard_input_mode {
|
|
||||||
SC_KEYBOARD_INPUT_MODE_INJECT,
|
|
||||||
SC_KEYBOARD_INPUT_MODE_HID,
|
|
||||||
};
|
|
||||||
|
|
||||||
#define SC_MAX_SHORTCUT_MODS 8
|
|
||||||
|
|
||||||
enum sc_shortcut_mod {
|
|
||||||
SC_MOD_LCTRL = 1 << 0,
|
|
||||||
SC_MOD_RCTRL = 1 << 1,
|
|
||||||
SC_MOD_LALT = 1 << 2,
|
|
||||||
SC_MOD_RALT = 1 << 3,
|
|
||||||
SC_MOD_LSUPER = 1 << 4,
|
|
||||||
SC_MOD_RSUPER = 1 << 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sc_shortcut_mods {
|
|
||||||
unsigned data[SC_MAX_SHORTCUT_MODS];
|
|
||||||
unsigned count;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sc_port_range {
|
|
||||||
uint16_t first;
|
|
||||||
uint16_t last;
|
|
||||||
};
|
|
||||||
|
|
||||||
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
|
|
||||||
|
|
||||||
struct scrcpy_options {
|
|
||||||
const char *serial;
|
|
||||||
const char *crop;
|
|
||||||
const char *record_filename;
|
|
||||||
const char *window_title;
|
|
||||||
const char *push_target;
|
|
||||||
const char *render_driver;
|
|
||||||
const char *codec_options;
|
|
||||||
const char *encoder_name;
|
|
||||||
#ifdef HAVE_V4L2
|
|
||||||
const char *v4l2_device;
|
|
||||||
#endif
|
|
||||||
enum sc_log_level log_level;
|
|
||||||
enum sc_record_format record_format;
|
|
||||||
enum sc_keyboard_input_mode keyboard_input_mode;
|
|
||||||
struct sc_port_range port_range;
|
|
||||||
struct sc_shortcut_mods shortcut_mods;
|
|
||||||
uint16_t max_size;
|
|
||||||
uint32_t bit_rate;
|
|
||||||
uint16_t max_fps;
|
|
||||||
enum sc_lock_video_orientation lock_video_orientation;
|
|
||||||
uint8_t rotation;
|
|
||||||
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
|
||||||
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
|
||||||
uint16_t window_width;
|
|
||||||
uint16_t window_height;
|
|
||||||
uint32_t display_id;
|
|
||||||
sc_tick display_buffer;
|
|
||||||
sc_tick v4l2_buffer;
|
|
||||||
bool show_touches;
|
|
||||||
bool fullscreen;
|
|
||||||
bool always_on_top;
|
|
||||||
bool control;
|
|
||||||
bool display;
|
|
||||||
bool turn_screen_off;
|
|
||||||
bool prefer_text;
|
|
||||||
bool window_borderless;
|
|
||||||
bool mipmaps;
|
|
||||||
bool stay_awake;
|
|
||||||
bool force_adb_forward;
|
|
||||||
bool disable_screensaver;
|
|
||||||
bool forward_key_repeat;
|
|
||||||
bool forward_all_clicks;
|
|
||||||
bool legacy_paste;
|
|
||||||
bool power_off_on_close;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern const struct scrcpy_options scrcpy_options_default;
|
|
||||||
|
|
||||||
#endif
|
|
@ -7,7 +7,7 @@
|
|||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
receiver_init(struct receiver *receiver, sc_socket control_socket) {
|
receiver_init(struct receiver *receiver, socket_t control_socket) {
|
||||||
bool ok = sc_mutex_init(&receiver->mutex);
|
bool ok = sc_mutex_init(&receiver->mutex);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -11,13 +11,13 @@
|
|||||||
// receive events from the device
|
// receive events from the device
|
||||||
// managed by the controller
|
// managed by the controller
|
||||||
struct receiver {
|
struct receiver {
|
||||||
sc_socket control_socket;
|
socket_t control_socket;
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
sc_mutex mutex;
|
sc_mutex mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
receiver_init(struct receiver *receiver, sc_socket control_socket);
|
receiver_init(struct receiver *receiver, socket_t control_socket);
|
||||||
|
|
||||||
void
|
void
|
||||||
receiver_destroy(struct receiver *receiver);
|
receiver_destroy(struct receiver *receiver);
|
||||||
|
@ -372,7 +372,7 @@ bool
|
|||||||
recorder_init(struct recorder *recorder,
|
recorder_init(struct recorder *recorder,
|
||||||
const char *filename,
|
const char *filename,
|
||||||
enum sc_record_format format,
|
enum sc_record_format format,
|
||||||
struct sc_size declared_frame_size) {
|
struct size declared_frame_size) {
|
||||||
recorder->filename = strdup(filename);
|
recorder->filename = strdup(filename);
|
||||||
if (!recorder->filename) {
|
if (!recorder->filename) {
|
||||||
LOGE("Could not strdup filename");
|
LOGE("Could not strdup filename");
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
#include "coords.h"
|
#include "coords.h"
|
||||||
#include "options.h"
|
#include "scrcpy.h"
|
||||||
#include "trait/packet_sink.h"
|
#include "trait/packet_sink.h"
|
||||||
#include "util/queue.h"
|
#include "util/queue.h"
|
||||||
#include "util/thread.h"
|
#include "util/thread.h"
|
||||||
@ -25,7 +25,7 @@ struct recorder {
|
|||||||
char *filename;
|
char *filename;
|
||||||
enum sc_record_format format;
|
enum sc_record_format format;
|
||||||
AVFormatContext *ctx;
|
AVFormatContext *ctx;
|
||||||
struct sc_size declared_frame_size;
|
struct size declared_frame_size;
|
||||||
bool header_written;
|
bool header_written;
|
||||||
|
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
@ -44,7 +44,7 @@ struct recorder {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
recorder_init(struct recorder *recorder, const char *filename,
|
recorder_init(struct recorder *recorder, const char *filename,
|
||||||
enum sc_record_format format, struct sc_size declared_frame_size);
|
enum sc_record_format format, struct size declared_frame_size);
|
||||||
|
|
||||||
void
|
void
|
||||||
recorder_destroy(struct recorder *recorder);
|
recorder_destroy(struct recorder *recorder);
|
||||||
|
147
app/src/scrcpy.c
147
app/src/scrcpy.c
@ -27,6 +27,7 @@
|
|||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include "stream.h"
|
#include "stream.h"
|
||||||
|
#include "tiny_xpm.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
@ -45,34 +46,24 @@ struct scrcpy {
|
|||||||
struct controller controller;
|
struct controller controller;
|
||||||
struct file_handler file_handler;
|
struct file_handler file_handler;
|
||||||
#ifdef HAVE_AOA_HID
|
#ifdef HAVE_AOA_HID
|
||||||
struct sc_aoa aoa;
|
struct aoa aoa;
|
||||||
#endif
|
#endif
|
||||||
union {
|
union {
|
||||||
struct sc_keyboard_inject keyboard_inject;
|
struct sc_keyboard_inject keyboard_inject;
|
||||||
#ifdef HAVE_AOA_HID
|
#ifdef HAVE_AOA_HID
|
||||||
struct sc_hid_keyboard keyboard_hid;
|
struct hid_keyboard keyboard_hid;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
struct sc_mouse_inject mouse_inject;
|
struct sc_mouse_inject mouse_inject;
|
||||||
struct input_manager input_manager;
|
struct input_manager input_manager;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline void
|
|
||||||
push_event(uint32_t type, const char *name) {
|
|
||||||
SDL_Event event;
|
|
||||||
event.type = type;
|
|
||||||
int ret = SDL_PushEvent(&event);
|
|
||||||
if (ret < 0) {
|
|
||||||
LOGE("Could not post %s event: %s", name, SDL_GetError());
|
|
||||||
// What could we do?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#define PUSH_EVENT(TYPE) push_event(TYPE, # TYPE)
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
|
BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
|
||||||
if (ctrl_type == CTRL_C_EVENT) {
|
if (ctrl_type == CTRL_C_EVENT) {
|
||||||
PUSH_EVENT(SDL_QUIT);
|
SDL_Event event;
|
||||||
|
event.type = SDL_QUIT;
|
||||||
|
SDL_PushEvent(&event);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@ -158,10 +149,6 @@ static enum event_result
|
|||||||
handle_event(struct scrcpy *s, const struct scrcpy_options *options,
|
handle_event(struct scrcpy *s, const struct scrcpy_options *options,
|
||||||
SDL_Event *event) {
|
SDL_Event *event) {
|
||||||
switch (event->type) {
|
switch (event->type) {
|
||||||
case EVENT_SERVER_DISCONNECTED:
|
|
||||||
LOGD("Server disconnected");
|
|
||||||
// Do nothing, will be managed by the "stream stopped" event
|
|
||||||
break;
|
|
||||||
case EVENT_STREAM_STOPPED:
|
case EVENT_STREAM_STOPPED:
|
||||||
LOGD("Video stream stopped");
|
LOGD("Video stream stopped");
|
||||||
return EVENT_RESULT_STOPPED_BY_EOS;
|
return EVENT_RESULT_STOPPED_BY_EOS;
|
||||||
@ -220,32 +207,6 @@ event_loop(struct scrcpy *s, const struct scrcpy_options *options) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
await_for_server(void) {
|
|
||||||
SDL_Event event;
|
|
||||||
while (SDL_WaitEvent(&event)) {
|
|
||||||
// Should never receive disconnected event before connected
|
|
||||||
assert(event.type != EVENT_SERVER_DISCONNECTED);
|
|
||||||
|
|
||||||
switch (event.type) {
|
|
||||||
case SDL_QUIT:
|
|
||||||
LOGD("User requested to quit");
|
|
||||||
return false;
|
|
||||||
case EVENT_SERVER_CONNECTION_FAILED:
|
|
||||||
LOGE("Server connection failed");
|
|
||||||
return false;
|
|
||||||
case EVENT_SERVER_CONNECTED:
|
|
||||||
LOGD("Server connected");
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGE("SDL_WaitEvent() error: %s", SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static SDL_LogPriority
|
static SDL_LogPriority
|
||||||
sdl_priority_from_av_level(int level) {
|
sdl_priority_from_av_level(int level) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
@ -288,31 +249,21 @@ stream_on_eos(struct stream *stream, void *userdata) {
|
|||||||
(void) stream;
|
(void) stream;
|
||||||
(void) userdata;
|
(void) userdata;
|
||||||
|
|
||||||
PUSH_EVENT(EVENT_STREAM_STOPPED);
|
SDL_Event stop_event;
|
||||||
|
stop_event.type = EVENT_STREAM_STOPPED;
|
||||||
|
SDL_PushEvent(&stop_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static unsigned
|
||||||
server_on_connection_failed(struct server *server, void *userdata) {
|
to_sc_mod(SDL_Keymod sdl_mod) {
|
||||||
(void) server;
|
unsigned mod = 0;
|
||||||
(void) userdata;
|
if (sdl_mod & KMOD_CAPS) {
|
||||||
|
mod |= SC_MOD_CAPSLOCK;
|
||||||
PUSH_EVENT(EVENT_SERVER_CONNECTION_FAILED);
|
|
||||||
}
|
}
|
||||||
|
if (sdl_mod & KMOD_NUM) {
|
||||||
static void
|
mod |= SC_MOD_NUMLOCK;
|
||||||
server_on_connected(struct server *server, void *userdata) {
|
|
||||||
(void) server;
|
|
||||||
(void) userdata;
|
|
||||||
|
|
||||||
PUSH_EVENT(EVENT_SERVER_CONNECTED);
|
|
||||||
}
|
}
|
||||||
|
return mod;
|
||||||
static void
|
|
||||||
server_on_disconnected(struct server *server, void *userdata) {
|
|
||||||
(void) server;
|
|
||||||
(void) userdata;
|
|
||||||
|
|
||||||
PUSH_EVENT(EVENT_SERVER_DISCONNECTED);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@ -320,6 +271,10 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
static struct scrcpy scrcpy;
|
static struct scrcpy scrcpy;
|
||||||
struct scrcpy *s = &scrcpy;
|
struct scrcpy *s = &scrcpy;
|
||||||
|
|
||||||
|
if (!server_init(&s->server)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
bool server_started = false;
|
bool server_started = false;
|
||||||
@ -355,18 +310,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
.force_adb_forward = options->force_adb_forward,
|
.force_adb_forward = options->force_adb_forward,
|
||||||
.power_off_on_close = options->power_off_on_close,
|
.power_off_on_close = options->power_off_on_close,
|
||||||
};
|
};
|
||||||
|
if (!server_start(&s->server, ¶ms)) {
|
||||||
static const struct server_callbacks cbs = {
|
|
||||||
.on_connection_failed = server_on_connection_failed,
|
|
||||||
.on_connected = server_on_connected,
|
|
||||||
.on_disconnected = server_on_disconnected,
|
|
||||||
};
|
|
||||||
if (!server_init(&s->server, ¶ms, &cbs, NULL)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO SDL_Init(SDL_INIT_EVENTS) before starting server
|
|
||||||
if (!server_start(&s->server)) {
|
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,15 +321,15 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Await for server without blocking Ctrl+C handling
|
char device_name[DEVICE_NAME_FIELD_LENGTH];
|
||||||
if (!await_for_server()) {
|
struct size frame_size;
|
||||||
|
|
||||||
|
if (!server_connect_to(&s->server, device_name, &frame_size)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct server_info *info = &s->server.info;
|
|
||||||
|
|
||||||
if (options->display && options->control) {
|
if (options->display && options->control) {
|
||||||
if (!file_handler_init(&s->file_handler, options->serial,
|
if (!file_handler_init(&s->file_handler, s->server.serial,
|
||||||
options->push_target)) {
|
options->push_target)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
@ -407,7 +351,7 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
if (!recorder_init(&s->recorder,
|
if (!recorder_init(&s->recorder,
|
||||||
options->record_filename,
|
options->record_filename,
|
||||||
options->record_format,
|
options->record_format,
|
||||||
info->frame_size)) {
|
frame_size)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
rec = &s->recorder;
|
rec = &s->recorder;
|
||||||
@ -453,11 +397,11 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
|
|
||||||
if (options->display) {
|
if (options->display) {
|
||||||
const char *window_title =
|
const char *window_title =
|
||||||
options->window_title ? options->window_title : info->device_name;
|
options->window_title ? options->window_title : device_name;
|
||||||
|
|
||||||
struct screen_params screen_params = {
|
struct screen_params screen_params = {
|
||||||
.window_title = window_title,
|
.window_title = window_title,
|
||||||
.frame_size = info->frame_size,
|
.frame_size = frame_size,
|
||||||
.always_on_top = options->always_on_top,
|
.always_on_top = options->always_on_top,
|
||||||
.window_x = options->window_x,
|
.window_x = options->window_x,
|
||||||
.window_y = options->window_y,
|
.window_y = options->window_y,
|
||||||
@ -480,8 +424,8 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
|
|
||||||
#ifdef HAVE_V4L2
|
#ifdef HAVE_V4L2
|
||||||
if (options->v4l2_device) {
|
if (options->v4l2_device) {
|
||||||
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device,
|
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, frame_size,
|
||||||
info->frame_size, options->v4l2_buffer)) {
|
options->v4l2_buffer)) {
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,20 +463,23 @@ scrcpy(struct scrcpy_options *options) {
|
|||||||
LOGI("Device serial: %s", serial);
|
LOGI("Device serial: %s", serial);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ok = sc_aoa_init(&s->aoa, serial);
|
bool ok = aoa_init(&s->aoa, serial);
|
||||||
free(serialno);
|
free(serialno);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
goto aoa_hid_end;
|
goto aoa_hid_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) {
|
if (!aoa_start(&s->aoa)) {
|
||||||
sc_aoa_destroy(&s->aoa);
|
aoa_destroy(&s->aoa);
|
||||||
goto aoa_hid_end;
|
goto aoa_hid_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sc_aoa_start(&s->aoa)) {
|
// FIXME: SDL_GetModState() always returns 0 here :/
|
||||||
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
unsigned mod = to_sc_mod(SDL_GetModState());
|
||||||
sc_aoa_destroy(&s->aoa);
|
if (!hid_keyboard_init(&s->keyboard_hid, &s->aoa, mod)) {
|
||||||
|
aoa_join(&s->aoa);
|
||||||
|
aoa_stop(&s->aoa);
|
||||||
|
aoa_destroy(&s->aoa);
|
||||||
goto aoa_hid_end;
|
goto aoa_hid_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -545,13 +492,13 @@ aoa_hid_end:
|
|||||||
if (!aoa_hid_ok) {
|
if (!aoa_hid_ok) {
|
||||||
LOGE("Failed to enable HID over AOA, "
|
LOGE("Failed to enable HID over AOA, "
|
||||||
"fallback to default keyboard injection method "
|
"fallback to default keyboard injection method "
|
||||||
"(-K/--hid-keyboard ignored)");
|
"(-K/--keyboard-hid ignored)");
|
||||||
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
|
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
LOGE("HID over AOA is not supported on this platform, "
|
LOGE("HID over AOA is not supported on this platform, "
|
||||||
"fallback to default keyboard injection method "
|
"fallback to default keyboard injection method "
|
||||||
"(-K/--hid-keyboard ignored)");
|
"(-K/--keyboard-hid ignored)");
|
||||||
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
|
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -582,8 +529,8 @@ end:
|
|||||||
// end-of-stream
|
// end-of-stream
|
||||||
#ifdef HAVE_AOA_HID
|
#ifdef HAVE_AOA_HID
|
||||||
if (aoa_hid_initialized) {
|
if (aoa_hid_initialized) {
|
||||||
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
hid_keyboard_destroy(&s->keyboard_hid);
|
||||||
sc_aoa_stop(&s->aoa);
|
aoa_stop(&s->aoa);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (controller_started) {
|
if (controller_started) {
|
||||||
@ -615,8 +562,8 @@ end:
|
|||||||
|
|
||||||
#ifdef HAVE_AOA_HID
|
#ifdef HAVE_AOA_HID
|
||||||
if (aoa_hid_initialized) {
|
if (aoa_hid_initialized) {
|
||||||
sc_aoa_join(&s->aoa);
|
aoa_join(&s->aoa);
|
||||||
sc_aoa_destroy(&s->aoa);
|
aoa_destroy(&s->aoa);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
153
app/src/scrcpy.h
153
app/src/scrcpy.h
@ -4,7 +4,158 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include "options.h"
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "util/tick.h"
|
||||||
|
|
||||||
|
enum sc_log_level {
|
||||||
|
SC_LOG_LEVEL_VERBOSE,
|
||||||
|
SC_LOG_LEVEL_DEBUG,
|
||||||
|
SC_LOG_LEVEL_INFO,
|
||||||
|
SC_LOG_LEVEL_WARN,
|
||||||
|
SC_LOG_LEVEL_ERROR,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum sc_record_format {
|
||||||
|
SC_RECORD_FORMAT_AUTO,
|
||||||
|
SC_RECORD_FORMAT_MP4,
|
||||||
|
SC_RECORD_FORMAT_MKV,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum sc_lock_video_orientation {
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
|
||||||
|
// lock the current orientation when scrcpy starts
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_1,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_2,
|
||||||
|
SC_LOCK_VIDEO_ORIENTATION_3,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum sc_keyboard_input_mode {
|
||||||
|
SC_KEYBOARD_INPUT_MODE_INJECT,
|
||||||
|
SC_KEYBOARD_INPUT_MODE_HID,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SC_MAX_SHORTCUT_MODS 8
|
||||||
|
|
||||||
|
enum sc_shortcut_mod {
|
||||||
|
SC_MOD_LCTRL = 1 << 0,
|
||||||
|
SC_MOD_RCTRL = 1 << 1,
|
||||||
|
SC_MOD_LALT = 1 << 2,
|
||||||
|
SC_MOD_RALT = 1 << 3,
|
||||||
|
SC_MOD_LSUPER = 1 << 4,
|
||||||
|
SC_MOD_RSUPER = 1 << 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_shortcut_mods {
|
||||||
|
unsigned data[SC_MAX_SHORTCUT_MODS];
|
||||||
|
unsigned count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_port_range {
|
||||||
|
uint16_t first;
|
||||||
|
uint16_t last;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
|
||||||
|
|
||||||
|
struct scrcpy_options {
|
||||||
|
const char *serial;
|
||||||
|
const char *crop;
|
||||||
|
const char *record_filename;
|
||||||
|
const char *window_title;
|
||||||
|
const char *push_target;
|
||||||
|
const char *render_driver;
|
||||||
|
const char *codec_options;
|
||||||
|
const char *encoder_name;
|
||||||
|
const char *v4l2_device;
|
||||||
|
enum sc_log_level log_level;
|
||||||
|
enum sc_record_format record_format;
|
||||||
|
enum sc_keyboard_input_mode keyboard_input_mode;
|
||||||
|
struct sc_port_range port_range;
|
||||||
|
struct sc_shortcut_mods shortcut_mods;
|
||||||
|
uint16_t max_size;
|
||||||
|
uint32_t bit_rate;
|
||||||
|
uint16_t max_fps;
|
||||||
|
enum sc_lock_video_orientation lock_video_orientation;
|
||||||
|
uint8_t rotation;
|
||||||
|
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
||||||
|
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
|
||||||
|
uint16_t window_width;
|
||||||
|
uint16_t window_height;
|
||||||
|
uint32_t display_id;
|
||||||
|
sc_tick display_buffer;
|
||||||
|
sc_tick v4l2_buffer;
|
||||||
|
bool show_touches;
|
||||||
|
bool fullscreen;
|
||||||
|
bool always_on_top;
|
||||||
|
bool control;
|
||||||
|
bool display;
|
||||||
|
bool turn_screen_off;
|
||||||
|
bool prefer_text;
|
||||||
|
bool window_borderless;
|
||||||
|
bool mipmaps;
|
||||||
|
bool stay_awake;
|
||||||
|
bool force_adb_forward;
|
||||||
|
bool disable_screensaver;
|
||||||
|
bool forward_key_repeat;
|
||||||
|
bool forward_all_clicks;
|
||||||
|
bool legacy_paste;
|
||||||
|
bool power_off_on_close;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SCRCPY_OPTIONS_DEFAULT { \
|
||||||
|
.serial = NULL, \
|
||||||
|
.crop = NULL, \
|
||||||
|
.record_filename = NULL, \
|
||||||
|
.window_title = NULL, \
|
||||||
|
.push_target = NULL, \
|
||||||
|
.render_driver = NULL, \
|
||||||
|
.codec_options = NULL, \
|
||||||
|
.encoder_name = NULL, \
|
||||||
|
.v4l2_device = NULL, \
|
||||||
|
.log_level = SC_LOG_LEVEL_INFO, \
|
||||||
|
.record_format = SC_RECORD_FORMAT_AUTO, \
|
||||||
|
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, \
|
||||||
|
.port_range = { \
|
||||||
|
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \
|
||||||
|
.last = DEFAULT_LOCAL_PORT_RANGE_LAST, \
|
||||||
|
}, \
|
||||||
|
.shortcut_mods = { \
|
||||||
|
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
|
||||||
|
.count = 2, \
|
||||||
|
}, \
|
||||||
|
.max_size = 0, \
|
||||||
|
.bit_rate = DEFAULT_BIT_RATE, \
|
||||||
|
.max_fps = 0, \
|
||||||
|
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED, \
|
||||||
|
.rotation = 0, \
|
||||||
|
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
|
||||||
|
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
|
||||||
|
.window_width = 0, \
|
||||||
|
.window_height = 0, \
|
||||||
|
.display_id = 0, \
|
||||||
|
.display_buffer = 0, \
|
||||||
|
.v4l2_buffer = 0, \
|
||||||
|
.show_touches = false, \
|
||||||
|
.fullscreen = false, \
|
||||||
|
.always_on_top = false, \
|
||||||
|
.control = true, \
|
||||||
|
.display = true, \
|
||||||
|
.turn_screen_off = false, \
|
||||||
|
.prefer_text = false, \
|
||||||
|
.window_borderless = false, \
|
||||||
|
.mipmaps = true, \
|
||||||
|
.stay_awake = false, \
|
||||||
|
.force_adb_forward = false, \
|
||||||
|
.disable_screensaver = false, \
|
||||||
|
.forward_key_repeat = true, \
|
||||||
|
.forward_all_clicks = false, \
|
||||||
|
.legacy_paste = false, \
|
||||||
|
.power_off_on_close = false, \
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
scrcpy(struct scrcpy_options *options);
|
scrcpy(struct scrcpy_options *options);
|
||||||
|
110
app/src/screen.c
110
app/src/screen.c
@ -5,8 +5,9 @@
|
|||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
#include "events.h"
|
#include "events.h"
|
||||||
#include "icon.h"
|
#include "icon.xpm"
|
||||||
#include "options.h"
|
#include "scrcpy.h"
|
||||||
|
#include "tiny_xpm.h"
|
||||||
#include "video_buffer.h"
|
#include "video_buffer.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
@ -14,9 +15,9 @@
|
|||||||
|
|
||||||
#define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink)
|
#define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink)
|
||||||
|
|
||||||
static inline struct sc_size
|
static inline struct size
|
||||||
get_rotated_size(struct sc_size size, int rotation) {
|
get_rotated_size(struct size size, int rotation) {
|
||||||
struct sc_size rotated_size;
|
struct size rotated_size;
|
||||||
if (rotation & 1) {
|
if (rotation & 1) {
|
||||||
rotated_size.width = size.height;
|
rotated_size.width = size.height;
|
||||||
rotated_size.height = size.width;
|
rotated_size.height = size.width;
|
||||||
@ -27,26 +28,26 @@ get_rotated_size(struct sc_size size, int rotation) {
|
|||||||
return rotated_size;
|
return rotated_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the window size in a struct sc_size
|
// get the window size in a struct size
|
||||||
static struct sc_size
|
static struct size
|
||||||
get_window_size(const struct screen *screen) {
|
get_window_size(const struct screen *screen) {
|
||||||
int width;
|
int width;
|
||||||
int height;
|
int height;
|
||||||
SDL_GetWindowSize(screen->window, &width, &height);
|
SDL_GetWindowSize(screen->window, &width, &height);
|
||||||
|
|
||||||
struct sc_size size;
|
struct size size;
|
||||||
size.width = width;
|
size.width = width;
|
||||||
size.height = height;
|
size.height = height;
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct sc_point
|
static struct point
|
||||||
get_window_position(const struct screen *screen) {
|
get_window_position(const struct screen *screen) {
|
||||||
int x;
|
int x;
|
||||||
int y;
|
int y;
|
||||||
SDL_GetWindowPosition(screen->window, &x, &y);
|
SDL_GetWindowPosition(screen->window, &x, &y);
|
||||||
|
|
||||||
struct sc_point point;
|
struct point point;
|
||||||
point.x = x;
|
point.x = x;
|
||||||
point.y = y;
|
point.y = y;
|
||||||
return point;
|
return point;
|
||||||
@ -54,7 +55,7 @@ get_window_position(const struct screen *screen) {
|
|||||||
|
|
||||||
// set the window size to be applied when fullscreen is disabled
|
// set the window size to be applied when fullscreen is disabled
|
||||||
static void
|
static void
|
||||||
set_window_size(struct screen *screen, struct sc_size new_size) {
|
set_window_size(struct screen *screen, struct size new_size) {
|
||||||
assert(!screen->fullscreen);
|
assert(!screen->fullscreen);
|
||||||
assert(!screen->maximized);
|
assert(!screen->maximized);
|
||||||
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
|
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
|
||||||
@ -62,7 +63,7 @@ set_window_size(struct screen *screen, struct sc_size new_size) {
|
|||||||
|
|
||||||
// get the preferred display bounds (i.e. the screen bounds with some margins)
|
// get the preferred display bounds (i.e. the screen bounds with some margins)
|
||||||
static bool
|
static bool
|
||||||
get_preferred_display_bounds(struct sc_size *bounds) {
|
get_preferred_display_bounds(struct size *bounds) {
|
||||||
SDL_Rect rect;
|
SDL_Rect rect;
|
||||||
#ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
|
#ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
|
||||||
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r))
|
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r))
|
||||||
@ -80,7 +81,7 @@ get_preferred_display_bounds(struct sc_size *bounds) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
is_optimal_size(struct sc_size current_size, struct sc_size content_size) {
|
is_optimal_size(struct size current_size, struct size content_size) {
|
||||||
// The size is optimal if we can recompute one dimension of the current
|
// The size is optimal if we can recompute one dimension of the current
|
||||||
// size from the other
|
// size from the other
|
||||||
return current_size.height == current_size.width * content_size.height
|
return current_size.height == current_size.width * content_size.height
|
||||||
@ -94,16 +95,16 @@ is_optimal_size(struct sc_size current_size, struct sc_size content_size) {
|
|||||||
// crops the black borders)
|
// crops the black borders)
|
||||||
// - it keeps the aspect ratio
|
// - it keeps the aspect ratio
|
||||||
// - it scales down to make it fit in the display_size
|
// - it scales down to make it fit in the display_size
|
||||||
static struct sc_size
|
static struct size
|
||||||
get_optimal_size(struct sc_size current_size, struct sc_size content_size) {
|
get_optimal_size(struct size current_size, struct size content_size) {
|
||||||
if (content_size.width == 0 || content_size.height == 0) {
|
if (content_size.width == 0 || content_size.height == 0) {
|
||||||
// avoid division by 0
|
// avoid division by 0
|
||||||
return current_size;
|
return current_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_size window_size;
|
struct size window_size;
|
||||||
|
|
||||||
struct sc_size display_size;
|
struct size display_size;
|
||||||
if (!get_preferred_display_bounds(&display_size)) {
|
if (!get_preferred_display_bounds(&display_size)) {
|
||||||
// could not get display bounds, do not constraint the size
|
// could not get display bounds, do not constraint the size
|
||||||
window_size.width = current_size.width;
|
window_size.width = current_size.width;
|
||||||
@ -135,10 +136,10 @@ get_optimal_size(struct sc_size current_size, struct sc_size content_size) {
|
|||||||
|
|
||||||
// initially, there is no current size, so use the frame size as current size
|
// initially, there is no current size, so use the frame size as current size
|
||||||
// req_width and req_height, if not 0, are the sizes requested by the user
|
// req_width and req_height, if not 0, are the sizes requested by the user
|
||||||
static inline struct sc_size
|
static inline struct size
|
||||||
get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
|
get_initial_optimal_size(struct size content_size, uint16_t req_width,
|
||||||
uint16_t req_height) {
|
uint16_t req_height) {
|
||||||
struct sc_size window_size;
|
struct size window_size;
|
||||||
if (!req_width && !req_height) {
|
if (!req_width && !req_height) {
|
||||||
window_size = get_optimal_size(content_size, content_size);
|
window_size = get_optimal_size(content_size, content_size);
|
||||||
} else {
|
} else {
|
||||||
@ -166,9 +167,9 @@ screen_update_content_rect(struct screen *screen) {
|
|||||||
int dh;
|
int dh;
|
||||||
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
||||||
|
|
||||||
struct sc_size content_size = screen->content_size;
|
struct size content_size = screen->content_size;
|
||||||
// The drawable size is the window size * the HiDPI scale
|
// The drawable size is the window size * the HiDPI scale
|
||||||
struct sc_size drawable_size = {dw, dh};
|
struct size drawable_size = {dw, dh};
|
||||||
|
|
||||||
SDL_Rect *rect = &screen->rect;
|
SDL_Rect *rect = &screen->rect;
|
||||||
|
|
||||||
@ -200,7 +201,7 @@ screen_update_content_rect(struct screen *screen) {
|
|||||||
static inline SDL_Texture *
|
static inline SDL_Texture *
|
||||||
create_texture(struct screen *screen) {
|
create_texture(struct screen *screen) {
|
||||||
SDL_Renderer *renderer = screen->renderer;
|
SDL_Renderer *renderer = screen->renderer;
|
||||||
struct sc_size size = screen->frame_size;
|
struct size size = screen->frame_size;
|
||||||
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
|
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
|
||||||
SDL_TEXTUREACCESS_STREAMING,
|
SDL_TEXTUREACCESS_STREAMING,
|
||||||
size.width, size.height);
|
size.width, size.height);
|
||||||
@ -282,33 +283,17 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
|
|||||||
(void) vb;
|
(void) vb;
|
||||||
struct screen *screen = userdata;
|
struct screen *screen = userdata;
|
||||||
|
|
||||||
// event_failed implies previous_skipped (the previous frame may not have
|
|
||||||
// been consumed if the event was not sent)
|
|
||||||
assert(!screen->event_failed || previous_skipped);
|
|
||||||
|
|
||||||
bool need_new_event;
|
|
||||||
if (previous_skipped) {
|
if (previous_skipped) {
|
||||||
fps_counter_add_skipped_frame(&screen->fps_counter);
|
fps_counter_add_skipped_frame(&screen->fps_counter);
|
||||||
// The EVENT_NEW_FRAME triggered for the previous frame will consume
|
// The EVENT_NEW_FRAME triggered for the previous frame will consume
|
||||||
// this new frame instead, unless the previous event failed
|
// this new frame instead
|
||||||
need_new_event = screen->event_failed;
|
|
||||||
} else {
|
} else {
|
||||||
need_new_event = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (need_new_event) {
|
|
||||||
static SDL_Event new_frame_event = {
|
static SDL_Event new_frame_event = {
|
||||||
.type = EVENT_NEW_FRAME,
|
.type = EVENT_NEW_FRAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Post the event on the UI thread
|
// Post the event on the UI thread
|
||||||
int ret = SDL_PushEvent(&new_frame_event);
|
SDL_PushEvent(&new_frame_event);
|
||||||
if (ret < 0) {
|
|
||||||
LOGW("Could not post new frame event: %s", SDL_GetError());
|
|
||||||
screen->event_failed = true;
|
|
||||||
} else {
|
|
||||||
screen->event_failed = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,7 +303,6 @@ screen_init(struct screen *screen, const struct screen_params *params) {
|
|||||||
screen->has_frame = false;
|
screen->has_frame = false;
|
||||||
screen->fullscreen = false;
|
screen->fullscreen = false;
|
||||||
screen->maximized = false;
|
screen->maximized = false;
|
||||||
screen->event_failed = false;
|
|
||||||
|
|
||||||
static const struct sc_video_buffer_callbacks cbs = {
|
static const struct sc_video_buffer_callbacks cbs = {
|
||||||
.on_new_frame = sc_video_buffer_on_new_frame,
|
.on_new_frame = sc_video_buffer_on_new_frame,
|
||||||
@ -347,12 +331,12 @@ screen_init(struct screen *screen, const struct screen_params *params) {
|
|||||||
if (screen->rotation) {
|
if (screen->rotation) {
|
||||||
LOGI("Initial display rotation set to %u", screen->rotation);
|
LOGI("Initial display rotation set to %u", screen->rotation);
|
||||||
}
|
}
|
||||||
struct sc_size content_size =
|
struct size content_size =
|
||||||
get_rotated_size(screen->frame_size, screen->rotation);
|
get_rotated_size(screen->frame_size, screen->rotation);
|
||||||
screen->content_size = content_size;
|
screen->content_size = content_size;
|
||||||
|
|
||||||
struct sc_size window_size =
|
struct size window_size = get_initial_optimal_size(content_size,
|
||||||
get_initial_optimal_size(content_size,params->window_width,
|
params->window_width,
|
||||||
params->window_height);
|
params->window_height);
|
||||||
uint32_t window_flags = SDL_WINDOW_HIDDEN
|
uint32_t window_flags = SDL_WINDOW_HIDDEN
|
||||||
| SDL_WINDOW_RESIZABLE
|
| SDL_WINDOW_RESIZABLE
|
||||||
@ -421,10 +405,10 @@ screen_init(struct screen *screen, const struct screen_params *params) {
|
|||||||
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Surface *icon = scrcpy_icon_load();
|
SDL_Surface *icon = read_xpm(icon_xpm);
|
||||||
if (icon) {
|
if (icon) {
|
||||||
SDL_SetWindowIcon(screen->window, icon);
|
SDL_SetWindowIcon(screen->window, icon);
|
||||||
scrcpy_icon_destroy(icon);
|
SDL_FreeSurface(icon);
|
||||||
} else {
|
} else {
|
||||||
LOGW("Could not load icon");
|
LOGW("Could not load icon");
|
||||||
}
|
}
|
||||||
@ -525,10 +509,10 @@ screen_destroy(struct screen *screen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
resize_for_content(struct screen *screen, struct sc_size old_content_size,
|
resize_for_content(struct screen *screen, struct size old_content_size,
|
||||||
struct sc_size new_content_size) {
|
struct size new_content_size) {
|
||||||
struct sc_size window_size = get_window_size(screen);
|
struct size window_size = get_window_size(screen);
|
||||||
struct sc_size target_size = {
|
struct size target_size = {
|
||||||
.width = (uint32_t) window_size.width * new_content_size.width
|
.width = (uint32_t) window_size.width * new_content_size.width
|
||||||
/ old_content_size.width,
|
/ old_content_size.width,
|
||||||
.height = (uint32_t) window_size.height * new_content_size.height
|
.height = (uint32_t) window_size.height * new_content_size.height
|
||||||
@ -539,7 +523,7 @@ resize_for_content(struct screen *screen, struct sc_size old_content_size,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
set_content_size(struct screen *screen, struct sc_size new_content_size) {
|
set_content_size(struct screen *screen, struct size new_content_size) {
|
||||||
if (!screen->fullscreen && !screen->maximized) {
|
if (!screen->fullscreen && !screen->maximized) {
|
||||||
resize_for_content(screen, screen->content_size, new_content_size);
|
resize_for_content(screen, screen->content_size, new_content_size);
|
||||||
} else if (!screen->resize_pending) {
|
} else if (!screen->resize_pending) {
|
||||||
@ -570,7 +554,7 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_size new_content_size =
|
struct size new_content_size =
|
||||||
get_rotated_size(screen->frame_size, rotation);
|
get_rotated_size(screen->frame_size, rotation);
|
||||||
|
|
||||||
set_content_size(screen, new_content_size);
|
set_content_size(screen, new_content_size);
|
||||||
@ -583,7 +567,7 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
|
|||||||
|
|
||||||
// recreate the texture and resize the window if the frame size has changed
|
// recreate the texture and resize the window if the frame size has changed
|
||||||
static bool
|
static bool
|
||||||
prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) {
|
prepare_for_frame(struct screen *screen, struct size new_frame_size) {
|
||||||
if (screen->frame_size.width != new_frame_size.width
|
if (screen->frame_size.width != new_frame_size.width
|
||||||
|| screen->frame_size.height != new_frame_size.height) {
|
|| screen->frame_size.height != new_frame_size.height) {
|
||||||
// frame dimension changed, destroy texture
|
// frame dimension changed, destroy texture
|
||||||
@ -591,7 +575,7 @@ prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) {
|
|||||||
|
|
||||||
screen->frame_size = new_frame_size;
|
screen->frame_size = new_frame_size;
|
||||||
|
|
||||||
struct sc_size new_content_size =
|
struct size new_content_size =
|
||||||
get_rotated_size(new_frame_size, screen->rotation);
|
get_rotated_size(new_frame_size, screen->rotation);
|
||||||
set_content_size(screen, new_content_size);
|
set_content_size(screen, new_content_size);
|
||||||
|
|
||||||
@ -632,7 +616,7 @@ screen_update_frame(struct screen *screen) {
|
|||||||
|
|
||||||
fps_counter_add_rendered_frame(&screen->fps_counter);
|
fps_counter_add_rendered_frame(&screen->fps_counter);
|
||||||
|
|
||||||
struct sc_size new_frame_size = {frame->width, frame->height};
|
struct size new_frame_size = {frame->width, frame->height};
|
||||||
if (!prepare_for_frame(screen, new_frame_size)) {
|
if (!prepare_for_frame(screen, new_frame_size)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -699,10 +683,10 @@ screen_resize_to_fit(struct screen *screen) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_point point = get_window_position(screen);
|
struct point point = get_window_position(screen);
|
||||||
struct sc_size window_size = get_window_size(screen);
|
struct size window_size = get_window_size(screen);
|
||||||
|
|
||||||
struct sc_size optimal_size =
|
struct size optimal_size =
|
||||||
get_optimal_size(window_size, screen->content_size);
|
get_optimal_size(window_size, screen->content_size);
|
||||||
|
|
||||||
// Center the window related to the device screen
|
// Center the window related to the device screen
|
||||||
@ -728,7 +712,7 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
|
|||||||
screen->maximized = false;
|
screen->maximized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_size content_size = screen->content_size;
|
struct size content_size = screen->content_size;
|
||||||
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
|
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
|
||||||
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
|
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
|
||||||
content_size.height);
|
content_size.height);
|
||||||
@ -783,7 +767,7 @@ screen_handle_event(struct screen *screen, SDL_Event *event) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_point
|
struct point
|
||||||
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
||||||
int32_t x, int32_t y) {
|
int32_t x, int32_t y) {
|
||||||
unsigned rotation = screen->rotation;
|
unsigned rotation = screen->rotation;
|
||||||
@ -797,7 +781,7 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
|
|||||||
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
|
y = (int64_t) (y - screen->rect.y) * h / screen->rect.h;
|
||||||
|
|
||||||
// rotate
|
// rotate
|
||||||
struct sc_point result;
|
struct point result;
|
||||||
switch (rotation) {
|
switch (rotation) {
|
||||||
case 0:
|
case 0:
|
||||||
result.x = x;
|
result.x = x;
|
||||||
@ -820,7 +804,7 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sc_point
|
struct point
|
||||||
screen_convert_window_to_frame_coords(struct screen *screen,
|
screen_convert_window_to_frame_coords(struct screen *screen,
|
||||||
int32_t x, int32_t y) {
|
int32_t x, int32_t y) {
|
||||||
screen_hidpi_scale_coords(screen, &x, &y);
|
screen_hidpi_scale_coords(screen, &x, &y);
|
||||||
|
@ -27,13 +27,13 @@ struct screen {
|
|||||||
SDL_Renderer *renderer;
|
SDL_Renderer *renderer;
|
||||||
SDL_Texture *texture;
|
SDL_Texture *texture;
|
||||||
struct sc_opengl gl;
|
struct sc_opengl gl;
|
||||||
struct sc_size frame_size;
|
struct size frame_size;
|
||||||
struct sc_size content_size; // rotated frame_size
|
struct size content_size; // rotated frame_size
|
||||||
|
|
||||||
bool resize_pending; // resize requested while fullscreen or maximized
|
bool resize_pending; // resize requested while fullscreen or maximized
|
||||||
// The content size the last time the window was not maximized or
|
// The content size the last time the window was not maximized or
|
||||||
// fullscreen (meaningful only when resize_pending is true)
|
// fullscreen (meaningful only when resize_pending is true)
|
||||||
struct sc_size windowed_content_size;
|
struct size windowed_content_size;
|
||||||
|
|
||||||
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
|
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
|
||||||
unsigned rotation;
|
unsigned rotation;
|
||||||
@ -44,14 +44,12 @@ struct screen {
|
|||||||
bool maximized;
|
bool maximized;
|
||||||
bool mipmaps;
|
bool mipmaps;
|
||||||
|
|
||||||
bool event_failed; // in case SDL_PushEvent() returned an error
|
|
||||||
|
|
||||||
AVFrame *frame;
|
AVFrame *frame;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct screen_params {
|
struct screen_params {
|
||||||
const char *window_title;
|
const char *window_title;
|
||||||
struct sc_size frame_size;
|
struct size frame_size;
|
||||||
bool always_on_top;
|
bool always_on_top;
|
||||||
|
|
||||||
int16_t window_x;
|
int16_t window_x;
|
||||||
@ -122,13 +120,13 @@ screen_handle_event(struct screen *screen, SDL_Event *event);
|
|||||||
|
|
||||||
// convert point from window coordinates to frame coordinates
|
// convert point from window coordinates to frame coordinates
|
||||||
// x and y are expressed in pixels
|
// x and y are expressed in pixels
|
||||||
struct sc_point
|
struct point
|
||||||
screen_convert_window_to_frame_coords(struct screen *screen,
|
screen_convert_window_to_frame_coords(struct screen *screen,
|
||||||
int32_t x, int32_t y);
|
int32_t x, int32_t y);
|
||||||
|
|
||||||
// convert point from drawable coordinates to frame coordinates
|
// convert point from drawable coordinates to frame coordinates
|
||||||
// x and y are expressed in pixels
|
// x and y are expressed in pixels
|
||||||
struct sc_point
|
struct point
|
||||||
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
||||||
int32_t x, int32_t y);
|
int32_t x, int32_t y);
|
||||||
|
|
||||||
|
362
app/src/server.c
362
app/src/server.c
@ -3,6 +3,7 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
#include <libgen.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <SDL2/SDL_timer.h>
|
#include <SDL2/SDL_timer.h>
|
||||||
#include <SDL2/SDL_platform.h>
|
#include <SDL2/SDL_platform.h>
|
||||||
@ -47,56 +48,54 @@ get_server_path(void) {
|
|||||||
LOGE("Could not allocate memory");
|
LOGE("Could not allocate memory");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
// the absolute path is hardcoded
|
||||||
|
return server_path;
|
||||||
#else
|
#else
|
||||||
char *server_path = get_local_file_path(SERVER_FILENAME);
|
|
||||||
if (!server_path) {
|
// use scrcpy-server in the same directory as the executable
|
||||||
LOGE("Could not get local file path, "
|
char *executable_path = get_executable_path();
|
||||||
|
if (!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
|
||||||
return strdup(SERVER_FILENAME);
|
return strdup(SERVER_FILENAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dirname() does not work correctly everywhere, so get the parent
|
||||||
|
// directory manually.
|
||||||
|
// See <https://github.com/Genymobile/scrcpy/issues/2619>
|
||||||
|
char *p = strrchr(executable_path, PATH_SEPARATOR);
|
||||||
|
if (!p) {
|
||||||
|
LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')",
|
||||||
|
executable_path, PATH_SEPARATOR);
|
||||||
|
free(executable_path);
|
||||||
|
return strdup(SERVER_FILENAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
*p = '\0'; // modify executable_path in place
|
||||||
|
char *dir = executable_path;
|
||||||
|
size_t dirlen = strlen(dir);
|
||||||
|
|
||||||
|
// sizeof(SERVER_FILENAME) gives statically the size including the null byte
|
||||||
|
size_t len = dirlen + 1 + sizeof(SERVER_FILENAME);
|
||||||
|
char *server_path = malloc(len);
|
||||||
|
if (!server_path) {
|
||||||
|
LOGE("Could not alloc server path string, "
|
||||||
|
"using " SERVER_FILENAME " from current directory");
|
||||||
|
free(executable_path);
|
||||||
|
return strdup(SERVER_FILENAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(server_path, dir, dirlen);
|
||||||
|
server_path[dirlen] = PATH_SEPARATOR;
|
||||||
|
memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME));
|
||||||
|
// the final null byte has been copied with SERVER_FILENAME
|
||||||
|
|
||||||
|
free(executable_path);
|
||||||
|
|
||||||
LOGD("Using server (portable): %s", server_path);
|
LOGD("Using server (portable): %s", server_path);
|
||||||
#endif
|
|
||||||
|
|
||||||
return server_path;
|
return server_path;
|
||||||
}
|
#endif
|
||||||
|
|
||||||
static void
|
|
||||||
server_params_destroy(struct server_params *params) {
|
|
||||||
// The server stores a copy of the params provided by the user
|
|
||||||
free((char *) params->serial);
|
|
||||||
free((char *) params->crop);
|
|
||||||
free((char *) params->codec_options);
|
|
||||||
free((char *) params->encoder_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
server_params_copy(struct server_params *dst, const struct server_params *src) {
|
|
||||||
*dst = *src;
|
|
||||||
|
|
||||||
// The params reference user-allocated memory, so we must copy them to
|
|
||||||
// handle them from another thread
|
|
||||||
|
|
||||||
#define COPY(FIELD) \
|
|
||||||
dst->FIELD = NULL; \
|
|
||||||
if (src->FIELD) { \
|
|
||||||
dst->FIELD = strdup(src->FIELD); \
|
|
||||||
if (!dst->FIELD) { \
|
|
||||||
goto error; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
COPY(serial);
|
|
||||||
COPY(crop);
|
|
||||||
COPY(codec_options);
|
|
||||||
COPY(encoder_name);
|
|
||||||
#undef COPY
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
error:
|
|
||||||
server_params_destroy(dst);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@ -141,14 +140,13 @@ disable_tunnel_forward(const char *serial, uint16_t local_port) {
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
disable_tunnel(struct server *server) {
|
disable_tunnel(struct server *server) {
|
||||||
const char *serial = server->params.serial;
|
|
||||||
if (server->tunnel_forward) {
|
if (server->tunnel_forward) {
|
||||||
return disable_tunnel_forward(serial, server->local_port);
|
return disable_tunnel_forward(server->serial, server->local_port);
|
||||||
}
|
}
|
||||||
return disable_tunnel_reverse(serial);
|
return disable_tunnel_reverse(server->serial);
|
||||||
}
|
}
|
||||||
|
|
||||||
static sc_socket
|
static socket_t
|
||||||
listen_on_port(uint16_t port) {
|
listen_on_port(uint16_t port) {
|
||||||
#define IPV4_LOCALHOST 0x7F000001
|
#define IPV4_LOCALHOST 0x7F000001
|
||||||
return net_listen(IPV4_LOCALHOST, port, 1);
|
return net_listen(IPV4_LOCALHOST, port, 1);
|
||||||
@ -157,10 +155,9 @@ listen_on_port(uint16_t port) {
|
|||||||
static bool
|
static bool
|
||||||
enable_tunnel_reverse_any_port(struct server *server,
|
enable_tunnel_reverse_any_port(struct server *server,
|
||||||
struct sc_port_range port_range) {
|
struct sc_port_range port_range) {
|
||||||
const char *serial = server->params.serial;
|
|
||||||
uint16_t port = port_range.first;
|
uint16_t port = port_range.first;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (!enable_tunnel_reverse(serial, port)) {
|
if (!enable_tunnel_reverse(server->serial, port)) {
|
||||||
// the command itself failed, it will fail on any port
|
// the command itself failed, it will fail on any port
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -172,14 +169,14 @@ enable_tunnel_reverse_any_port(struct server *server,
|
|||||||
// need to try to connect until the server socket is listening on the
|
// need to try to connect until the server socket is listening on the
|
||||||
// device.
|
// device.
|
||||||
server->server_socket = listen_on_port(port);
|
server->server_socket = listen_on_port(port);
|
||||||
if (server->server_socket != SC_INVALID_SOCKET) {
|
if (server->server_socket != INVALID_SOCKET) {
|
||||||
// success
|
// success
|
||||||
server->local_port = port;
|
server->local_port = port;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// failure, disable tunnel and try another port
|
// failure, disable tunnel and try another port
|
||||||
if (!disable_tunnel_reverse(serial)) {
|
if (!disable_tunnel_reverse(server->serial)) {
|
||||||
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
|
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,11 +202,9 @@ static bool
|
|||||||
enable_tunnel_forward_any_port(struct server *server,
|
enable_tunnel_forward_any_port(struct server *server,
|
||||||
struct sc_port_range port_range) {
|
struct sc_port_range port_range) {
|
||||||
server->tunnel_forward = true;
|
server->tunnel_forward = true;
|
||||||
|
|
||||||
const char *serial = server->params.serial;
|
|
||||||
uint16_t port = port_range.first;
|
uint16_t port = port_range.first;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (enable_tunnel_forward(serial, port)) {
|
if (enable_tunnel_forward(server->serial, port)) {
|
||||||
// success
|
// success
|
||||||
server->local_port = port;
|
server->local_port = port;
|
||||||
return true;
|
return true;
|
||||||
@ -271,8 +266,6 @@ log_level_to_server_string(enum sc_log_level level) {
|
|||||||
|
|
||||||
static process_t
|
static process_t
|
||||||
execute_server(struct server *server, const struct server_params *params) {
|
execute_server(struct server *server, const struct server_params *params) {
|
||||||
const char *serial = server->params.serial;
|
|
||||||
|
|
||||||
char max_size_string[6];
|
char max_size_string[6];
|
||||||
char bit_rate_string[11];
|
char bit_rate_string[11];
|
||||||
char max_fps_string[6];
|
char max_fps_string[6];
|
||||||
@ -330,14 +323,14 @@ 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(serial, cmd, ARRAY_LEN(cmd));
|
return adb_execute(server->serial, cmd, ARRAY_LEN(cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
static sc_socket
|
static socket_t
|
||||||
connect_and_read_byte(uint16_t port) {
|
connect_and_read_byte(uint16_t port) {
|
||||||
sc_socket socket = net_connect(IPV4_LOCALHOST, port);
|
socket_t socket = net_connect(IPV4_LOCALHOST, port);
|
||||||
if (socket == SC_INVALID_SOCKET) {
|
if (socket == INVALID_SOCKET) {
|
||||||
return SC_INVALID_SOCKET;
|
return INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
char byte;
|
char byte;
|
||||||
@ -346,127 +339,72 @@ connect_and_read_byte(uint16_t port) {
|
|||||||
if (net_recv(socket, &byte, 1) != 1) {
|
if (net_recv(socket, &byte, 1) != 1) {
|
||||||
// the server is not listening yet behind the adb tunnel
|
// the server is not listening yet behind the adb tunnel
|
||||||
net_close(socket);
|
net_close(socket);
|
||||||
return SC_INVALID_SOCKET;
|
return INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
return socket;
|
return socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
static sc_socket
|
static socket_t
|
||||||
connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
|
connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
|
||||||
do {
|
do {
|
||||||
LOGD("Remaining connection attempts: %d", (int) attempts);
|
LOGD("Remaining connection attempts: %d", (int) attempts);
|
||||||
sc_socket socket = connect_and_read_byte(port);
|
socket_t socket = connect_and_read_byte(port);
|
||||||
if (socket != SC_INVALID_SOCKET) {
|
if (socket != INVALID_SOCKET) {
|
||||||
// it worked!
|
// it worked!
|
||||||
return socket;
|
return socket;
|
||||||
}
|
}
|
||||||
// TODO use mutex + condvar + bool stopped
|
|
||||||
if (attempts) {
|
if (attempts) {
|
||||||
SDL_Delay(delay);
|
SDL_Delay(delay);
|
||||||
}
|
}
|
||||||
} while (--attempts > 0);
|
} while (--attempts > 0);
|
||||||
return SC_INVALID_SOCKET;
|
return INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
close_socket(socket_t socket) {
|
||||||
|
assert(socket != INVALID_SOCKET);
|
||||||
|
net_shutdown(socket, SHUT_RDWR);
|
||||||
|
if (!net_close(socket)) {
|
||||||
|
LOGW("Could not close socket");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
server_init(struct server *server, const struct server_params *params,
|
server_init(struct server *server) {
|
||||||
const struct server_callbacks *cbs, void *cbs_userdata) {
|
server->serial = NULL;
|
||||||
bool ok = server_params_copy(&server->params, params);
|
|
||||||
if (!ok) {
|
|
||||||
LOGE("Could not copy server params");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
server->process = PROCESS_NONE;
|
server->process = PROCESS_NONE;
|
||||||
|
atomic_flag_clear_explicit(&server->server_socket_closed,
|
||||||
|
memory_order_relaxed);
|
||||||
|
|
||||||
ok = sc_mutex_init(&server->mutex);
|
bool ok = sc_mutex_init(&server->mutex);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
server_params_destroy(&server->params);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = sc_cond_init(&server->process_terminated_cond);
|
ok = sc_cond_init(&server->process_terminated_cond);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
sc_mutex_destroy(&server->mutex);
|
sc_mutex_destroy(&server->mutex);
|
||||||
server_params_destroy(&server->params);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
server->process_terminated = false;
|
server->process_terminated = false;
|
||||||
server->connected = false;
|
|
||||||
|
|
||||||
server->server_socket = SC_INVALID_SOCKET;
|
server->server_socket = INVALID_SOCKET;
|
||||||
server->video_socket = SC_INVALID_SOCKET;
|
server->video_socket = INVALID_SOCKET;
|
||||||
server->control_socket = SC_INVALID_SOCKET;
|
server->control_socket = INVALID_SOCKET;
|
||||||
|
|
||||||
server->local_port = 0;
|
server->local_port = 0;
|
||||||
|
|
||||||
server->tunnel_enabled = false;
|
server->tunnel_enabled = false;
|
||||||
server->tunnel_forward = false;
|
server->tunnel_forward = false;
|
||||||
|
|
||||||
assert(cbs);
|
|
||||||
assert(cbs->on_connection_failed);
|
|
||||||
assert(cbs->on_connected);
|
|
||||||
assert(cbs->on_disconnected);
|
|
||||||
|
|
||||||
server->cbs = cbs;
|
|
||||||
server->cbs_userdata = cbs_userdata;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
run_server_connect(void *data) {
|
run_wait_server(void *data) {
|
||||||
struct server *server = data;
|
struct server *server = data;
|
||||||
|
|
||||||
if (!server_connect_to(server, &server->info)) {
|
|
||||||
server->cbs->on_connection_failed(server, server->cbs_userdata);
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
server->connected = true;
|
|
||||||
server->cbs->on_connected(server, server->cbs_userdata);
|
|
||||||
|
|
||||||
end:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
run_server(void *data) {
|
|
||||||
struct server *server = data;
|
|
||||||
|
|
||||||
const struct server_params *params = &server->params;
|
|
||||||
|
|
||||||
if (!push_server(params->serial)) {
|
|
||||||
server->cbs->on_connection_failed(server, server->cbs_userdata);
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!enable_tunnel_any_port(server, params->port_range,
|
|
||||||
params->force_adb_forward)) {
|
|
||||||
server->cbs->on_connection_failed(server, server->cbs_userdata);
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
server->process = execute_server(server, params);
|
|
||||||
if (server->process == PROCESS_NONE) {
|
|
||||||
server->cbs->on_connection_failed(server, server->cbs_userdata);
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_thread connect_thread;
|
|
||||||
bool ok = sc_thread_create(&connect_thread, run_server_connect,
|
|
||||||
"server-connect", server);
|
|
||||||
if (!ok) {
|
|
||||||
LOGW("Could not create thread, killing the server...");
|
|
||||||
process_terminate(server->process);
|
|
||||||
server->cbs->on_connection_failed(server, server->cbs_userdata);
|
|
||||||
process_wait(server->process, false); // ignore exit code
|
process_wait(server->process, false); // ignore exit code
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
process_wait(server->process, false); // ignore exit code
|
|
||||||
LOGD("Server terminated");
|
|
||||||
|
|
||||||
sc_mutex_lock(&server->mutex);
|
sc_mutex_lock(&server->mutex);
|
||||||
server->process_terminated = true;
|
server->process_terminated = true;
|
||||||
@ -475,31 +413,75 @@ run_server(void *data) {
|
|||||||
|
|
||||||
// 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
|
||||||
if (server->server_socket != SC_INVALID_SOCKET) {
|
if (server->server_socket != INVALID_SOCKET
|
||||||
// Unblock any accept()
|
&& !atomic_flag_test_and_set(&server->server_socket_closed)) {
|
||||||
net_interrupt(server->server_socket);
|
// On Linux, accept() is unblocked by shutdown(), but on Windows, it is
|
||||||
|
// unblocked by closesocket(). Therefore, call both (close_socket()).
|
||||||
|
close_socket(server->server_socket);
|
||||||
}
|
}
|
||||||
|
LOGD("Server terminated");
|
||||||
sc_thread_join(&connect_thread, NULL);
|
|
||||||
|
|
||||||
// Written by connect_thread, sc_thread_join() provides the necessary
|
|
||||||
// memory barrier
|
|
||||||
if (server->connected) {
|
|
||||||
server->cbs->on_disconnected(server, server->cbs_userdata);
|
|
||||||
}
|
|
||||||
// Otherwise, ->on_connection_failed() is already called
|
|
||||||
|
|
||||||
end:
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
server_start(struct server *server) {
|
server_start(struct server *server, const struct server_params *params) {
|
||||||
return sc_thread_create(&server->thread, run_server, "server", server);
|
if (params->serial) {
|
||||||
|
server->serial = strdup(params->serial);
|
||||||
|
if (!server->serial) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!push_server(params->serial)) {
|
||||||
|
/* server->serial will be freed on server_destroy() */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enable_tunnel_any_port(server, params->port_range,
|
||||||
|
params->force_adb_forward)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// server will connect to our server socket
|
||||||
|
server->process = execute_server(server, params);
|
||||||
|
if (server->process == PROCESS_NONE) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the server process dies before connecting to the server socket, then
|
||||||
|
// the client will be stuck forever on accept(). To avoid the problem, we
|
||||||
|
// must be able to wake up the accept() call when the server dies. To keep
|
||||||
|
// things simple and multiplatform, just spawn a new thread waiting for the
|
||||||
|
// server process and calling shutdown()/close() on the server socket if
|
||||||
|
// necessary to wake up any accept() blocking call.
|
||||||
|
bool ok = sc_thread_create(&server->wait_server_thread, run_wait_server,
|
||||||
|
"wait-server", server);
|
||||||
|
if (!ok) {
|
||||||
|
process_terminate(server->process);
|
||||||
|
process_wait(server->process, true); // ignore exit code
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
server->tunnel_enabled = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
error:
|
||||||
|
if (!server->tunnel_forward) {
|
||||||
|
bool was_closed =
|
||||||
|
atomic_flag_test_and_set(&server->server_socket_closed);
|
||||||
|
// the thread is not started, the flag could not be already set
|
||||||
|
assert(!was_closed);
|
||||||
|
(void) was_closed;
|
||||||
|
close_socket(server->server_socket);
|
||||||
|
}
|
||||||
|
disable_tunnel(server);
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
device_read_info(sc_socket device_socket, struct server_info *info) {
|
device_read_info(socket_t device_socket, char *device_name, struct size *size) {
|
||||||
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
|
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
|
||||||
ssize_t r = net_recv_all(device_socket, buf, sizeof(buf));
|
ssize_t r = net_recv_all(device_socket, buf, sizeof(buf));
|
||||||
if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
|
if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
|
||||||
@ -508,48 +490,49 @@ device_read_info(sc_socket device_socket, struct server_info *info) {
|
|||||||
}
|
}
|
||||||
// in case the client sends garbage
|
// in case the client sends garbage
|
||||||
buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
|
buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
|
||||||
memcpy(info->device_name, (char *) buf, sizeof(info->device_name));
|
// strcpy is safe here, since name contains at least
|
||||||
|
// DEVICE_NAME_FIELD_LENGTH bytes and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
|
||||||
info->frame_size.width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8)
|
strcpy(device_name, (char *) buf);
|
||||||
|
size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8)
|
||||||
| buf[DEVICE_NAME_FIELD_LENGTH + 1];
|
| buf[DEVICE_NAME_FIELD_LENGTH + 1];
|
||||||
info->frame_size.height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8)
|
size->height = (buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8)
|
||||||
| buf[DEVICE_NAME_FIELD_LENGTH + 3];
|
| buf[DEVICE_NAME_FIELD_LENGTH + 3];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
server_connect_to(struct server *server, struct server_info *info) {
|
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 == SC_INVALID_SOCKET) {
|
if (server->video_socket == INVALID_SOCKET) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
server->control_socket = net_accept(server->server_socket);
|
server->control_socket = net_accept(server->server_socket);
|
||||||
if (server->control_socket == SC_INVALID_SOCKET) {
|
if (server->control_socket == INVALID_SOCKET) {
|
||||||
// the video_socket will be cleaned up on destroy
|
// the video_socket will be cleaned up on destroy
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we don't need the server socket anymore
|
// we don't need the server socket anymore
|
||||||
if (!net_close(server->server_socket)) {
|
if (!atomic_flag_test_and_set(&server->server_socket_closed)) {
|
||||||
LOGW("Could not close server socket on connect");
|
// close it from here
|
||||||
|
close_socket(server->server_socket);
|
||||||
|
// otherwise, it is closed by run_wait_server()
|
||||||
}
|
}
|
||||||
// Do not attempt to close it again on server_destroy()
|
|
||||||
server->server_socket = SC_INVALID_SOCKET;
|
|
||||||
} else {
|
} else {
|
||||||
uint32_t attempts = 100;
|
uint32_t attempts = 100;
|
||||||
uint32_t delay = 100; // ms
|
uint32_t delay = 100; // ms
|
||||||
server->video_socket =
|
server->video_socket =
|
||||||
connect_to_server(server->local_port, attempts, delay);
|
connect_to_server(server->local_port, attempts, delay);
|
||||||
if (server->video_socket == SC_INVALID_SOCKET) {
|
if (server->video_socket == INVALID_SOCKET) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we know that the device is listening, we don't need several attempts
|
// we know that the device is listening, we don't need several attempts
|
||||||
server->control_socket =
|
server->control_socket =
|
||||||
net_connect(IPV4_LOCALHOST, server->local_port);
|
net_connect(IPV4_LOCALHOST, server->local_port);
|
||||||
if (server->control_socket == SC_INVALID_SOCKET) {
|
if (server->control_socket == INVALID_SOCKET) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -559,25 +542,20 @@ server_connect_to(struct server *server, struct server_info *info) {
|
|||||||
server->tunnel_enabled = false;
|
server->tunnel_enabled = false;
|
||||||
|
|
||||||
// The sockets will be closed on stop if device_read_info() fails
|
// The sockets will be closed on stop if device_read_info() fails
|
||||||
return device_read_info(server->video_socket, info);
|
return device_read_info(server->video_socket, device_name, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
server_stop(struct server *server) {
|
server_stop(struct server *server) {
|
||||||
if (server->server_socket != SC_INVALID_SOCKET) {
|
if (server->server_socket != INVALID_SOCKET
|
||||||
if (!net_interrupt(server->server_socket)) {
|
&& !atomic_flag_test_and_set(&server->server_socket_closed)) {
|
||||||
LOGW("Could not interrupt server socket");
|
close_socket(server->server_socket);
|
||||||
}
|
}
|
||||||
|
if (server->video_socket != INVALID_SOCKET) {
|
||||||
|
close_socket(server->video_socket);
|
||||||
}
|
}
|
||||||
if (server->video_socket != SC_INVALID_SOCKET) {
|
if (server->control_socket != INVALID_SOCKET) {
|
||||||
if (!net_interrupt(server->video_socket)) {
|
close_socket(server->control_socket);
|
||||||
LOGW("Could not interrupt video socket");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (server->control_socket != SC_INVALID_SOCKET) {
|
|
||||||
if (!net_interrupt(server->control_socket)) {
|
|
||||||
LOGW("Could not interrupt control socket");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(server->process != PROCESS_NONE);
|
assert(server->process != PROCESS_NONE);
|
||||||
@ -608,29 +586,13 @@ server_stop(struct server *server) {
|
|||||||
process_terminate(server->process);
|
process_terminate(server->process);
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_thread_join(&server->thread, NULL);
|
sc_thread_join(&server->wait_server_thread, NULL);
|
||||||
process_close(server->process);
|
process_close(server->process);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
server_destroy(struct server *server) {
|
server_destroy(struct server *server) {
|
||||||
if (server->server_socket != SC_INVALID_SOCKET) {
|
free(server->serial);
|
||||||
if (!net_close(server->server_socket)) {
|
|
||||||
LOGW("Could not close server socket");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (server->video_socket != SC_INVALID_SOCKET) {
|
|
||||||
if (!net_close(server->video_socket)) {
|
|
||||||
LOGW("Could not close video socket");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (server->control_socket != SC_INVALID_SOCKET) {
|
|
||||||
if (!net_close(server->control_socket)) {
|
|
||||||
LOGW("Could not close control socket");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server_params_destroy(&server->params);
|
|
||||||
sc_cond_destroy(&server->process_terminated_cond);
|
sc_cond_destroy(&server->process_terminated_cond);
|
||||||
sc_mutex_destroy(&server->mutex);
|
sc_mutex_destroy(&server->mutex);
|
||||||
}
|
}
|
||||||
|
@ -9,15 +9,27 @@
|
|||||||
|
|
||||||
#include "adb.h"
|
#include "adb.h"
|
||||||
#include "coords.h"
|
#include "coords.h"
|
||||||
#include "options.h"
|
#include "scrcpy.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/net.h"
|
#include "util/net.h"
|
||||||
#include "util/thread.h"
|
#include "util/thread.h"
|
||||||
|
|
||||||
#define DEVICE_NAME_FIELD_LENGTH 64
|
struct server {
|
||||||
struct server_info {
|
char *serial;
|
||||||
char device_name[DEVICE_NAME_FIELD_LENGTH];
|
process_t process;
|
||||||
struct sc_size frame_size;
|
sc_thread wait_server_thread;
|
||||||
|
atomic_flag server_socket_closed;
|
||||||
|
|
||||||
|
sc_mutex mutex;
|
||||||
|
sc_cond process_terminated_cond;
|
||||||
|
bool process_terminated;
|
||||||
|
|
||||||
|
socket_t server_socket; // only used if !tunnel_forward
|
||||||
|
socket_t video_socket;
|
||||||
|
socket_t control_socket;
|
||||||
|
uint16_t local_port; // selected from port_range
|
||||||
|
bool tunnel_enabled;
|
||||||
|
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
|
||||||
};
|
};
|
||||||
|
|
||||||
struct server_params {
|
struct server_params {
|
||||||
@ -39,50 +51,19 @@ struct server_params {
|
|||||||
bool power_off_on_close;
|
bool power_off_on_close;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct server {
|
// init default values
|
||||||
// The internal allocated strings are copies owned by the server
|
|
||||||
struct server_params params;
|
|
||||||
|
|
||||||
process_t process;
|
|
||||||
sc_thread thread;
|
|
||||||
|
|
||||||
sc_mutex mutex;
|
|
||||||
sc_cond process_terminated_cond;
|
|
||||||
bool process_terminated;
|
|
||||||
|
|
||||||
bool connected; // written by connect_thread
|
|
||||||
struct server_info info; // initialized once connected
|
|
||||||
|
|
||||||
sc_socket server_socket; // only used if !tunnel_forward
|
|
||||||
sc_socket video_socket;
|
|
||||||
sc_socket control_socket;
|
|
||||||
uint16_t local_port; // selected from port_range
|
|
||||||
bool tunnel_enabled;
|
|
||||||
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
|
|
||||||
|
|
||||||
const struct server_callbacks *cbs;
|
|
||||||
void *cbs_userdata;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct server_callbacks {
|
|
||||||
void (*on_connection_failed)(struct server *server, void *userdata);
|
|
||||||
void (*on_connected)(struct server *server, void *userdata);
|
|
||||||
void (*on_disconnected)(struct server *server, void *userdata);
|
|
||||||
};
|
|
||||||
|
|
||||||
// init the server with the given params
|
|
||||||
bool
|
bool
|
||||||
server_init(struct server *server, const struct server_params *params,
|
server_init(struct server *server);
|
||||||
const struct server_callbacks *cbs, void *cbs_userdata);
|
|
||||||
|
|
||||||
// push, enable tunnel et start the server
|
// push, enable tunnel et start the server
|
||||||
bool
|
bool
|
||||||
server_start(struct server *server);
|
server_start(struct server *server, const struct server_params *params);
|
||||||
|
|
||||||
|
#define DEVICE_NAME_FIELD_LENGTH 64
|
||||||
// block until the communication with the server is established
|
// block until the communication with the server is established
|
||||||
// device_name must point to a buffer of at least DEVICE_NAME_FIELD_LENGTH bytes
|
// device_name must point to a buffer of at least DEVICE_NAME_FIELD_LENGTH bytes
|
||||||
bool
|
bool
|
||||||
server_connect_to(struct server *server, struct server_info *info);
|
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
|
||||||
|
@ -260,7 +260,7 @@ end:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
stream_init(struct stream *stream, sc_socket socket,
|
stream_init(struct stream *stream, socket_t socket,
|
||||||
const struct stream_callbacks *cbs, void *cbs_userdata) {
|
const struct stream_callbacks *cbs, void *cbs_userdata) {
|
||||||
stream->socket = socket;
|
stream->socket = socket;
|
||||||
stream->pending = NULL;
|
stream->pending = NULL;
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
#define STREAM_MAX_SINKS 2
|
#define STREAM_MAX_SINKS 2
|
||||||
|
|
||||||
struct stream {
|
struct stream {
|
||||||
sc_socket socket;
|
socket_t socket;
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
|
|
||||||
struct sc_packet_sink *sinks[STREAM_MAX_SINKS];
|
struct sc_packet_sink *sinks[STREAM_MAX_SINKS];
|
||||||
@ -35,7 +35,7 @@ struct stream_callbacks {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
stream_init(struct stream *stream, sc_socket socket,
|
stream_init(struct stream *stream, socket_t socket,
|
||||||
const struct stream_callbacks *cbs, void *cbs_userdata);
|
const struct stream_callbacks *cbs, void *cbs_userdata);
|
||||||
|
|
||||||
void
|
void
|
||||||
|
119
app/src/tiny_xpm.c
Normal file
119
app/src/tiny_xpm.c
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
#include "tiny_xpm.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
struct index {
|
||||||
|
char c;
|
||||||
|
uint32_t color;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool
|
||||||
|
find_color(struct index *index, int len, char c, uint32_t *color) {
|
||||||
|
// there are typically very few color, so it's ok to iterate over the array
|
||||||
|
for (int i = 0; i < len; ++i) {
|
||||||
|
if (index[i].c == c) {
|
||||||
|
*color = index[i].color;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*color = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We encounter some problems with SDL2_image on MSYS2 (Windows),
|
||||||
|
// so here is our own XPM parsing not to depend on SDL_image.
|
||||||
|
//
|
||||||
|
// We do not hardcode the binary image to keep some flexibility to replace the
|
||||||
|
// icon easily (just by replacing icon.xpm).
|
||||||
|
//
|
||||||
|
// Parameter is not "const char *" because XPM formats are generally stored in a
|
||||||
|
// (non-const) "char *"
|
||||||
|
SDL_Surface *
|
||||||
|
read_xpm(char *xpm[]) {
|
||||||
|
#ifndef NDEBUG
|
||||||
|
// patch the XPM to change the icon color in debug mode
|
||||||
|
xpm[2] = ". c #CC00CC";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
char *endptr;
|
||||||
|
// *** No error handling, assume the XPM source is valid ***
|
||||||
|
// (it's in our source repo)
|
||||||
|
// Assertions are only checked in debug
|
||||||
|
int width = strtol(xpm[0], &endptr, 10);
|
||||||
|
int height = strtol(endptr + 1, &endptr, 10);
|
||||||
|
int colors = strtol(endptr + 1, &endptr, 10);
|
||||||
|
int chars = strtol(endptr + 1, &endptr, 10);
|
||||||
|
|
||||||
|
// sanity checks
|
||||||
|
assert(0 <= width && width < 256);
|
||||||
|
assert(0 <= height && height < 256);
|
||||||
|
assert(0 <= colors && colors < 256);
|
||||||
|
assert(chars == 1); // this implementation does not support more
|
||||||
|
|
||||||
|
(void) chars;
|
||||||
|
|
||||||
|
// init index
|
||||||
|
struct index index[colors];
|
||||||
|
for (int i = 0; i < colors; ++i) {
|
||||||
|
const char *line = xpm[1+i];
|
||||||
|
index[i].c = line[0];
|
||||||
|
assert(line[1] == '\t');
|
||||||
|
assert(line[2] == 'c');
|
||||||
|
assert(line[3] == ' ');
|
||||||
|
if (line[4] == '#') {
|
||||||
|
index[i].color = 0xff000000 | strtol(&line[5], &endptr, 0x10);
|
||||||
|
assert(*endptr == '\0');
|
||||||
|
} else {
|
||||||
|
assert(!strcmp("None", &line[4]));
|
||||||
|
index[i].color = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse image
|
||||||
|
uint32_t *pixels = SDL_malloc(4 * width * height);
|
||||||
|
if (!pixels) {
|
||||||
|
LOGE("Could not allocate icon memory");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
for (int y = 0; y < height; ++y) {
|
||||||
|
const char *line = xpm[1 + colors + y];
|
||||||
|
for (int x = 0; x < width; ++x) {
|
||||||
|
char c = line[x];
|
||||||
|
uint32_t color;
|
||||||
|
bool color_found = find_color(index, colors, c, &color);
|
||||||
|
assert(color_found);
|
||||||
|
(void) color_found;
|
||||||
|
pixels[y * width + x] = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
||||||
|
uint32_t amask = 0x000000ff;
|
||||||
|
uint32_t rmask = 0x0000ff00;
|
||||||
|
uint32_t gmask = 0x00ff0000;
|
||||||
|
uint32_t bmask = 0xff000000;
|
||||||
|
#else // little endian, like x86
|
||||||
|
uint32_t amask = 0xff000000;
|
||||||
|
uint32_t rmask = 0x00ff0000;
|
||||||
|
uint32_t gmask = 0x0000ff00;
|
||||||
|
uint32_t bmask = 0x000000ff;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(pixels,
|
||||||
|
width, height,
|
||||||
|
32, 4 * width,
|
||||||
|
rmask, gmask, bmask, amask);
|
||||||
|
if (!surface) {
|
||||||
|
LOGE("Could not create icon surface");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
// make the surface own the raw pixels
|
||||||
|
surface->flags &= ~SDL_PREALLOC;
|
||||||
|
return surface;
|
||||||
|
}
|
11
app/src/tiny_xpm.h
Normal file
11
app/src/tiny_xpm.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#ifndef TINYXPM_H
|
||||||
|
#define TINYXPM_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
SDL_Surface *
|
||||||
|
read_xpm(char *xpm[]);
|
||||||
|
|
||||||
|
#endif
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#include <SDL2/SDL_log.h>
|
#include <SDL2/SDL_log.h>
|
||||||
|
|
||||||
#include "options.h"
|
#include "scrcpy.h"
|
||||||
|
|
||||||
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
||||||
#define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
#define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <SDL2/SDL_platform.h>
|
#include <SDL2/SDL_platform.h>
|
||||||
|
|
||||||
@ -8,7 +7,6 @@
|
|||||||
|
|
||||||
#ifdef __WINDOWS__
|
#ifdef __WINDOWS__
|
||||||
typedef int socklen_t;
|
typedef int socklen_t;
|
||||||
typedef SOCKET sc_raw_socket;
|
|
||||||
#else
|
#else
|
||||||
# include <sys/types.h>
|
# include <sys/types.h>
|
||||||
# include <sys/socket.h>
|
# include <sys/socket.h>
|
||||||
@ -19,9 +17,122 @@
|
|||||||
typedef struct sockaddr_in SOCKADDR_IN;
|
typedef struct sockaddr_in SOCKADDR_IN;
|
||||||
typedef struct sockaddr SOCKADDR;
|
typedef struct sockaddr SOCKADDR;
|
||||||
typedef struct in_addr IN_ADDR;
|
typedef struct in_addr IN_ADDR;
|
||||||
typedef int sc_raw_socket;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static void
|
||||||
|
net_perror(const char *s) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
int error = WSAGetLastError();
|
||||||
|
char *wsa_message;
|
||||||
|
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
||||||
|
NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||||
|
(char *) &wsa_message, 0, NULL);
|
||||||
|
// no explicit '\n', wsa_message already contains a trailing '\n'
|
||||||
|
fprintf(stderr, "%s: [%d] %s", s, error, wsa_message);
|
||||||
|
LocalFree(wsa_message);
|
||||||
|
#else
|
||||||
|
perror(s);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
socket_t
|
||||||
|
net_connect(uint32_t addr, uint16_t port) {
|
||||||
|
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (sock == INVALID_SOCKET) {
|
||||||
|
net_perror("socket");
|
||||||
|
return INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
SOCKADDR_IN sin;
|
||||||
|
sin.sin_family = AF_INET;
|
||||||
|
sin.sin_addr.s_addr = htonl(addr);
|
||||||
|
sin.sin_port = htons(port);
|
||||||
|
|
||||||
|
if (connect(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
|
||||||
|
net_perror("connect");
|
||||||
|
net_close(sock);
|
||||||
|
return INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket_t
|
||||||
|
net_listen(uint32_t addr, uint16_t port, int backlog) {
|
||||||
|
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (sock == INVALID_SOCKET) {
|
||||||
|
net_perror("socket");
|
||||||
|
return INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
int reuse = 1;
|
||||||
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
|
||||||
|
sizeof(reuse)) == -1) {
|
||||||
|
net_perror("setsockopt(SO_REUSEADDR)");
|
||||||
|
}
|
||||||
|
|
||||||
|
SOCKADDR_IN sin;
|
||||||
|
sin.sin_family = AF_INET;
|
||||||
|
sin.sin_addr.s_addr = htonl(addr); // htonl() harmless on INADDR_ANY
|
||||||
|
sin.sin_port = htons(port);
|
||||||
|
|
||||||
|
if (bind(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
|
||||||
|
net_perror("bind");
|
||||||
|
net_close(sock);
|
||||||
|
return INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listen(sock, backlog) == SOCKET_ERROR) {
|
||||||
|
net_perror("listen");
|
||||||
|
net_close(sock);
|
||||||
|
return INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket_t
|
||||||
|
net_accept(socket_t server_socket) {
|
||||||
|
SOCKADDR_IN csin;
|
||||||
|
socklen_t sinsize = sizeof(csin);
|
||||||
|
return accept(server_socket, (SOCKADDR *) &csin, &sinsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
net_recv(socket_t socket, void *buf, size_t len) {
|
||||||
|
return recv(socket, buf, len, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
net_recv_all(socket_t socket, void *buf, size_t len) {
|
||||||
|
return recv(socket, buf, len, MSG_WAITALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
net_send(socket_t socket, const void *buf, size_t len) {
|
||||||
|
return send(socket, buf, len, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
net_send_all(socket_t socket, const void *buf, size_t len) {
|
||||||
|
size_t copied = 0;
|
||||||
|
while (len > 0) {
|
||||||
|
ssize_t w = send(socket, buf, len, 0);
|
||||||
|
if (w == -1) {
|
||||||
|
return copied ? (ssize_t) copied : -1;
|
||||||
|
}
|
||||||
|
len -= w;
|
||||||
|
buf = (char *) buf + w;
|
||||||
|
copied += w;
|
||||||
|
}
|
||||||
|
return copied;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
net_shutdown(socket_t socket, int how) {
|
||||||
|
return !shutdown(socket, how);
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
net_init(void) {
|
net_init(void) {
|
||||||
#ifdef __WINDOWS__
|
#ifdef __WINDOWS__
|
||||||
@ -42,189 +153,11 @@ net_cleanup(void) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline sc_socket
|
|
||||||
wrap(sc_raw_socket sock) {
|
|
||||||
#ifdef __WINDOWS__
|
|
||||||
if (sock == INVALID_SOCKET) {
|
|
||||||
return SC_INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sc_socket_windows *socket = malloc(sizeof(*socket));
|
|
||||||
if (!socket) {
|
|
||||||
closesocket(sock);
|
|
||||||
return SC_INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
socket->socket = sock;
|
|
||||||
socket->closed = (atomic_flag) ATOMIC_FLAG_INIT;
|
|
||||||
|
|
||||||
return socket;
|
|
||||||
#else
|
|
||||||
return sock;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline sc_raw_socket
|
|
||||||
unwrap(sc_socket socket) {
|
|
||||||
#ifdef __WINDOWS__
|
|
||||||
if (socket == SC_INVALID_SOCKET) {
|
|
||||||
return INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
return socket->socket;
|
|
||||||
#else
|
|
||||||
return socket;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
net_perror(const char *s) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
int error = WSAGetLastError();
|
|
||||||
char *wsa_message;
|
|
||||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
|
||||||
NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
||||||
(char *) &wsa_message, 0, NULL);
|
|
||||||
// no explicit '\n', wsa_message already contains a trailing '\n'
|
|
||||||
fprintf(stderr, "%s: [%d] %s", s, error, wsa_message);
|
|
||||||
LocalFree(wsa_message);
|
|
||||||
#else
|
|
||||||
perror(s);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_socket
|
|
||||||
net_connect(uint32_t addr, uint16_t port) {
|
|
||||||
sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
sc_socket sock = wrap(raw_sock);
|
|
||||||
if (sock == SC_INVALID_SOCKET) {
|
|
||||||
net_perror("socket");
|
|
||||||
return SC_INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
SOCKADDR_IN sin;
|
|
||||||
sin.sin_family = AF_INET;
|
|
||||||
sin.sin_addr.s_addr = htonl(addr);
|
|
||||||
sin.sin_port = htons(port);
|
|
||||||
|
|
||||||
if (connect(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
|
|
||||||
net_perror("connect");
|
|
||||||
net_close(sock);
|
|
||||||
return SC_INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sock;
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_socket
|
|
||||||
net_listen(uint32_t addr, uint16_t port, int backlog) {
|
|
||||||
sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
sc_socket sock = wrap(raw_sock);
|
|
||||||
if (sock == SC_INVALID_SOCKET) {
|
|
||||||
net_perror("socket");
|
|
||||||
return SC_INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
int reuse = 1;
|
|
||||||
if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
|
|
||||||
sizeof(reuse)) == -1) {
|
|
||||||
net_perror("setsockopt(SO_REUSEADDR)");
|
|
||||||
}
|
|
||||||
|
|
||||||
SOCKADDR_IN sin;
|
|
||||||
sin.sin_family = AF_INET;
|
|
||||||
sin.sin_addr.s_addr = htonl(addr); // htonl() harmless on INADDR_ANY
|
|
||||||
sin.sin_port = htons(port);
|
|
||||||
|
|
||||||
if (bind(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
|
|
||||||
net_perror("bind");
|
|
||||||
net_close(sock);
|
|
||||||
return SC_INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listen(raw_sock, backlog) == SOCKET_ERROR) {
|
|
||||||
net_perror("listen");
|
|
||||||
net_close(sock);
|
|
||||||
return SC_INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sock;
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_socket
|
|
||||||
net_accept(sc_socket server_socket) {
|
|
||||||
sc_raw_socket raw_server_socket = unwrap(server_socket);
|
|
||||||
|
|
||||||
SOCKADDR_IN csin;
|
|
||||||
socklen_t sinsize = sizeof(csin);
|
|
||||||
sc_raw_socket raw_sock =
|
|
||||||
accept(raw_server_socket, (SOCKADDR *) &csin, &sinsize);
|
|
||||||
|
|
||||||
return wrap(raw_sock);
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t
|
|
||||||
net_recv(sc_socket socket, void *buf, size_t len) {
|
|
||||||
sc_raw_socket raw_sock = unwrap(socket);
|
|
||||||
return recv(raw_sock, buf, len, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t
|
|
||||||
net_recv_all(sc_socket socket, void *buf, size_t len) {
|
|
||||||
sc_raw_socket raw_sock = unwrap(socket);
|
|
||||||
return recv(raw_sock, buf, len, MSG_WAITALL);
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t
|
|
||||||
net_send(sc_socket socket, const void *buf, size_t len) {
|
|
||||||
sc_raw_socket raw_sock = unwrap(socket);
|
|
||||||
return send(raw_sock, buf, len, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t
|
|
||||||
net_send_all(sc_socket socket, const void *buf, size_t len) {
|
|
||||||
size_t copied = 0;
|
|
||||||
while (len > 0) {
|
|
||||||
ssize_t w = net_send(socket, buf, len);
|
|
||||||
if (w == -1) {
|
|
||||||
return copied ? (ssize_t) copied : -1;
|
|
||||||
}
|
|
||||||
len -= w;
|
|
||||||
buf = (char *) buf + w;
|
|
||||||
copied += w;
|
|
||||||
}
|
|
||||||
return copied;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
net_interrupt(sc_socket socket) {
|
net_close(socket_t socket) {
|
||||||
assert(socket != SC_INVALID_SOCKET);
|
|
||||||
|
|
||||||
sc_raw_socket raw_sock = unwrap(socket);
|
|
||||||
|
|
||||||
#ifdef __WINDOWS__
|
#ifdef __WINDOWS__
|
||||||
if (!atomic_flag_test_and_set(&socket->closed)) {
|
return !closesocket(socket);
|
||||||
return !closesocket(raw_sock);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
#else
|
#else
|
||||||
return !shutdown(raw_sock, SHUT_RDWR);
|
return !close(socket);
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
bool
|
|
||||||
net_close(sc_socket socket) {
|
|
||||||
sc_raw_socket raw_sock = unwrap(socket);
|
|
||||||
|
|
||||||
#ifdef __WINDOWS__
|
|
||||||
bool ret = true;
|
|
||||||
if (!atomic_flag_test_and_set(&socket->closed)) {
|
|
||||||
ret = !closesocket(raw_sock);
|
|
||||||
}
|
|
||||||
free(socket);
|
|
||||||
return ret;
|
|
||||||
#else
|
|
||||||
return !close(raw_sock);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -8,20 +8,15 @@
|
|||||||
#include <SDL2/SDL_platform.h>
|
#include <SDL2/SDL_platform.h>
|
||||||
|
|
||||||
#ifdef __WINDOWS__
|
#ifdef __WINDOWS__
|
||||||
|
|
||||||
# include <winsock2.h>
|
# include <winsock2.h>
|
||||||
# include <stdatomic.h>
|
#define SHUT_RD SD_RECEIVE
|
||||||
# define SC_INVALID_SOCKET NULL
|
#define SHUT_WR SD_SEND
|
||||||
typedef struct sc_socket_windows {
|
#define SHUT_RDWR SD_BOTH
|
||||||
SOCKET socket;
|
typedef SOCKET socket_t;
|
||||||
atomic_flag closed;
|
#else
|
||||||
} *sc_socket;
|
|
||||||
|
|
||||||
#else // not __WINDOWS__
|
|
||||||
|
|
||||||
# include <sys/socket.h>
|
# include <sys/socket.h>
|
||||||
# define SC_INVALID_SOCKET -1
|
# define INVALID_SOCKET -1
|
||||||
typedef int sc_socket;
|
typedef int socket_t;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@ -30,36 +25,33 @@ net_init(void);
|
|||||||
void
|
void
|
||||||
net_cleanup(void);
|
net_cleanup(void);
|
||||||
|
|
||||||
sc_socket
|
socket_t
|
||||||
net_connect(uint32_t addr, uint16_t port);
|
net_connect(uint32_t addr, uint16_t port);
|
||||||
|
|
||||||
sc_socket
|
socket_t
|
||||||
net_listen(uint32_t addr, uint16_t port, int backlog);
|
net_listen(uint32_t addr, uint16_t port, int backlog);
|
||||||
|
|
||||||
sc_socket
|
socket_t
|
||||||
net_accept(sc_socket server_socket);
|
net_accept(socket_t server_socket);
|
||||||
|
|
||||||
// the _all versions wait/retry until len bytes have been written/read
|
// the _all versions wait/retry until len bytes have been written/read
|
||||||
ssize_t
|
ssize_t
|
||||||
net_recv(sc_socket socket, void *buf, size_t len);
|
net_recv(socket_t socket, void *buf, size_t len);
|
||||||
|
|
||||||
ssize_t
|
ssize_t
|
||||||
net_recv_all(sc_socket socket, void *buf, size_t len);
|
net_recv_all(socket_t socket, void *buf, size_t len);
|
||||||
|
|
||||||
ssize_t
|
ssize_t
|
||||||
net_send(sc_socket socket, const void *buf, size_t len);
|
net_send(socket_t socket, const void *buf, size_t len);
|
||||||
|
|
||||||
ssize_t
|
ssize_t
|
||||||
net_send_all(sc_socket socket, const void *buf, size_t len);
|
net_send_all(socket_t socket, const void *buf, size_t len);
|
||||||
|
|
||||||
// Shutdown the socket (or close on Windows) so that any blocking send() or
|
// how is SHUT_RD (read), SHUT_WR (write) or SHUT_RDWR (both)
|
||||||
// recv() are interrupted.
|
|
||||||
bool
|
bool
|
||||||
net_interrupt(sc_socket socket);
|
net_shutdown(socket_t socket, int how);
|
||||||
|
|
||||||
// Close the socket.
|
|
||||||
// A socket must always be closed, even if net_interrupt() has been called.
|
|
||||||
bool
|
bool
|
||||||
net_close(sc_socket socket);
|
net_close(socket_t socket);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#include "process.h"
|
#include "process.h"
|
||||||
|
|
||||||
#include <libgen.h>
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@ -21,47 +20,6 @@ process_check_success(process_t proc, const char *name, bool close) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *
|
|
||||||
get_local_file_path(const char *name) {
|
|
||||||
char *executable_path = get_executable_path();
|
|
||||||
if (!executable_path) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dirname() does not work correctly everywhere, so get the parent
|
|
||||||
// directory manually.
|
|
||||||
// See <https://github.com/Genymobile/scrcpy/issues/2619>
|
|
||||||
char *p = strrchr(executable_path, PATH_SEPARATOR);
|
|
||||||
if (!p) {
|
|
||||||
LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')",
|
|
||||||
executable_path, PATH_SEPARATOR);
|
|
||||||
free(executable_path);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
*p = '\0'; // modify executable_path in place
|
|
||||||
char *dir = executable_path;
|
|
||||||
size_t dirlen = strlen(dir);
|
|
||||||
size_t namelen = strlen(name);
|
|
||||||
|
|
||||||
size_t len = dirlen + namelen + 2; // +2: '/' and '\0'
|
|
||||||
char *file_path = malloc(len);
|
|
||||||
if (!file_path) {
|
|
||||||
LOGE("Could not alloc path");
|
|
||||||
free(executable_path);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(file_path, dir, dirlen);
|
|
||||||
file_path[dirlen] = PATH_SEPARATOR;
|
|
||||||
// namelen + 1 to copy the final '\0'
|
|
||||||
memcpy(&file_path[dirlen + 1], name, namelen + 1);
|
|
||||||
|
|
||||||
free(executable_path);
|
|
||||||
|
|
||||||
return file_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
ssize_t
|
ssize_t
|
||||||
read_pipe_all(pipe_t pipe, char *data, size_t len) {
|
read_pipe_all(pipe_t pipe, char *data, size_t len) {
|
||||||
size_t copied = 0;
|
size_t copied = 0;
|
||||||
|
@ -84,11 +84,6 @@ search_executable(const char *file);
|
|||||||
char *
|
char *
|
||||||
get_executable_path(void);
|
get_executable_path(void);
|
||||||
|
|
||||||
// Return the absolute path of a file in the same directory as he executable.
|
|
||||||
// May be NULL on error. To be freed by free().
|
|
||||||
char *
|
|
||||||
get_local_file_path(const char *name);
|
|
||||||
|
|
||||||
// returns true if the file exists and is not a directory
|
// returns true if the file exists and is not a directory
|
||||||
bool
|
bool
|
||||||
is_regular_file(const char *path);
|
is_regular_file(const char *path);
|
||||||
|
@ -359,7 +359,7 @@ sc_v4l2_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
||||||
struct sc_size frame_size, sc_tick buffering_time) {
|
struct size frame_size, sc_tick buffering_time) {
|
||||||
vs->device_name = strdup(device_name);
|
vs->device_name = strdup(device_name);
|
||||||
if (!vs->device_name) {
|
if (!vs->device_name) {
|
||||||
LOGE("Could not strdup v4l2 device name");
|
LOGE("Could not strdup v4l2 device name");
|
||||||
|
@ -18,7 +18,7 @@ struct sc_v4l2_sink {
|
|||||||
AVCodecContext *encoder_ctx;
|
AVCodecContext *encoder_ctx;
|
||||||
|
|
||||||
char *device_name;
|
char *device_name;
|
||||||
struct sc_size frame_size;
|
struct size frame_size;
|
||||||
sc_tick buffering_time;
|
sc_tick buffering_time;
|
||||||
|
|
||||||
sc_thread thread;
|
sc_thread thread;
|
||||||
@ -34,7 +34,7 @@ struct sc_v4l2_sink {
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
sc_v4l2_sink_init(struct sc_v4l2_sink *vs, const char *device_name,
|
||||||
struct sc_size frame_size, sc_tick buffering_time);
|
struct size frame_size, sc_tick buffering_time);
|
||||||
|
|
||||||
void
|
void
|
||||||
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);
|
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "cli.h"
|
#include "cli.h"
|
||||||
#include "options.h"
|
#include "scrcpy.h"
|
||||||
|
|
||||||
static void test_flag_version(void) {
|
static void test_flag_version(void) {
|
||||||
struct scrcpy_cli_args args = {
|
struct scrcpy_cli_args args = {
|
||||||
.opts = scrcpy_options_default,
|
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||||
.help = false,
|
.help = false,
|
||||||
.version = false,
|
.version = false,
|
||||||
};
|
};
|
||||||
@ -23,7 +23,7 @@ static void test_flag_version(void) {
|
|||||||
|
|
||||||
static void test_flag_help(void) {
|
static void test_flag_help(void) {
|
||||||
struct scrcpy_cli_args args = {
|
struct scrcpy_cli_args args = {
|
||||||
.opts = scrcpy_options_default,
|
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||||
.help = false,
|
.help = false,
|
||||||
.version = false,
|
.version = false,
|
||||||
};
|
};
|
||||||
@ -38,7 +38,7 @@ static void test_flag_help(void) {
|
|||||||
|
|
||||||
static void test_options(void) {
|
static void test_options(void) {
|
||||||
struct scrcpy_cli_args args = {
|
struct scrcpy_cli_args args = {
|
||||||
.opts = scrcpy_options_default,
|
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||||
.help = false,
|
.help = false,
|
||||||
.version = false,
|
.version = false,
|
||||||
};
|
};
|
||||||
@ -100,7 +100,7 @@ static void test_options(void) {
|
|||||||
|
|
||||||
static void test_options2(void) {
|
static void test_options2(void) {
|
||||||
struct scrcpy_cli_args args = {
|
struct scrcpy_cli_args args = {
|
||||||
.opts = scrcpy_options_default,
|
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||||
.help = false,
|
.help = false,
|
||||||
.version = false,
|
.version = false,
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.0.2'
|
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
@ -2,7 +2,7 @@ apply plugin: 'checkstyle'
|
|||||||
check.dependsOn 'checkstyle'
|
check.dependsOn 'checkstyle'
|
||||||
|
|
||||||
checkstyle {
|
checkstyle {
|
||||||
toolVersion = '9.0.1'
|
toolVersion = '6.19'
|
||||||
}
|
}
|
||||||
|
|
||||||
task checkstyle(type: Checkstyle) {
|
task checkstyle(type: Checkstyle) {
|
||||||
|
@ -37,14 +37,6 @@ page at http://checkstyle.sourceforge.net/config.html -->
|
|||||||
|
|
||||||
<module name="SuppressWarningsFilter"/>
|
<module name="SuppressWarningsFilter"/>
|
||||||
|
|
||||||
<module name="LineLength">
|
|
||||||
<!-- what is a good max value? -->
|
|
||||||
<property name="max" value="150" />
|
|
||||||
<!-- ignore lines like "$File: //depot/... $" -->
|
|
||||||
<property name="ignorePattern" value="\$File.*\$" />
|
|
||||||
<property name="severity" value="info" />
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<module name="TreeWalker">
|
<module name="TreeWalker">
|
||||||
|
|
||||||
<!-- Checks for Naming Conventions. -->
|
<!-- Checks for Naming Conventions. -->
|
||||||
@ -80,6 +72,13 @@ page at http://checkstyle.sourceforge.net/config.html -->
|
|||||||
|
|
||||||
<!-- Checks for Size Violations. -->
|
<!-- Checks for Size Violations. -->
|
||||||
<!-- See http://checkstyle.sf.net/config_sizes.html -->
|
<!-- See http://checkstyle.sf.net/config_sizes.html -->
|
||||||
|
<module name="LineLength">
|
||||||
|
<!-- what is a good max value? -->
|
||||||
|
<property name="max" value="150" />
|
||||||
|
<!-- ignore lines like "$File: //depot/... $" -->
|
||||||
|
<property name="ignorePattern" value="\$File.*\$" />
|
||||||
|
<property name="severity" value="info" />
|
||||||
|
</module>
|
||||||
<module name="MethodLength" />
|
<module name="MethodLength" />
|
||||||
<module name="ParameterNumber">
|
<module name="ParameterNumber">
|
||||||
<property name="ignoreOverriddenMethods" value="true"/>
|
<property name="ignoreOverriddenMethods" value="true"/>
|
||||||
@ -153,6 +152,26 @@ page at http://checkstyle.sourceforge.net/config.html -->
|
|||||||
</module>
|
</module>
|
||||||
<module name="UpperEll" />
|
<module name="UpperEll" />
|
||||||
|
|
||||||
|
<module name="FileContentsHolder" />
|
||||||
|
<!-- Required by comment suppression filters -->
|
||||||
|
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="SuppressionFilter">
|
||||||
|
<!--<property name="file" value="team-props/checkstyle/checkstyle-suppressions.xml" />-->
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- Enable suppression comments -->
|
||||||
|
<module name="SuppressionCommentFilter">
|
||||||
|
<property name="offCommentFormat" value="CHECKSTYLE IGNORE\s+(\S+)" />
|
||||||
|
<property name="onCommentFormat" value="CHECKSTYLE END IGNORE\s+(\S+)" />
|
||||||
|
<property name="checkFormat" value="$1" />
|
||||||
|
</module>
|
||||||
|
<module name="SuppressWithNearbyCommentFilter">
|
||||||
|
<!-- Syntax is "SUPPRESS CHECKSTYLE name" -->
|
||||||
|
<property name="commentFormat" value="SUPPRESS CHECKSTYLE (\w+)" />
|
||||||
|
<property name="checkFormat" value="$1" />
|
||||||
|
<property name="influenceFormat" value="1" />
|
||||||
</module>
|
</module>
|
||||||
|
|
||||||
</module>
|
</module>
|
||||||
|
BIN
data/icon.png
BIN
data/icon.png
Binary file not shown.
Before Width: | Height: | Size: 6.4 KiB |
@ -1,16 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" version="1.1">
|
|
||||||
<path style="opacity:0.2" d="m 16.846877,12 c -1.116351,0 -2.227419,0.912229 -2.015075,2 l 3.122973,16 -5.596557,11.109375 C 11.959876,41.871734 11.885244,42.336988 12.177176,43 c 0.278672,0.632897 0.998812,1 1.747448,1 H 24 34.075375 c 0.748637,0 1.468777,-0.367103 1.747448,-1 0.291932,-0.663012 0.217302,-1.128266 -0.181041,-1.890625 L 30.045225,30 33.168198,14 c 0.212344,-1.087771 -0.898724,-2 -2.015075,-2 H 24 Z"/>
|
|
||||||
<path style="fill:#cccccc" d="m 16.846877,11 c -1.116351,0 -2.227419,0.912229 -2.015075,2 l 3.122973,16 -5.596557,11.109375 C 11.959876,40.871734 11.885244,41.336988 12.177176,42 c 0.278672,0.632897 0.998812,1 1.747448,1 H 24 34.075375 c 0.748637,0 1.468777,-0.367103 1.747448,-1 0.291932,-0.663012 0.217302,-1.128266 -0.181041,-1.890625 L 30.045225,29 33.168198,13 c 0.212344,-1.087771 -0.898724,-2 -2.015075,-2 H 24 Z"/>
|
|
||||||
<rect style="opacity:0.2" width="40" height="32" x="4" y="6" rx="2" ry="2"/>
|
|
||||||
<path style="fill:#e4e4e4" d="m 4,33 v 2 c 0,1.108 0.892,2 2,2 h 36 c 1.108,0 2,-0.892 2,-2 v -2 z"/>
|
|
||||||
<path style="opacity:0.1" d="m 11.494141,15 a 1.5,1.5 0 0 0 -0.832032,0.255859 1.5,1.5 0 0 0 -0.40625,2.082032 l 3.13086,4.654297 C 10.404945,24.606192 8.4012371,28.299159 8.0019531,32.460938 7.9284599,34.000879 9.5546875,34 9.5546875,34 H 38.40625 c 0,0 1.672856,-3.38e-4 1.591797,-1.617188 -0.416529,-4.131451 -2.415618,-7.796833 -5.380859,-10.394531 l 3.126953,-4.65039 a 1.5,1.5 0 0 0 -0.40625,-2.082032 1.5,1.5 0 0 0 -1.125,-0.228515 1.5,1.5 0 0 0 -0.957032,0.634765 l -3.072265,4.566407 C 29.78649,18.814887 26.990024,18 24.001953,18 c -2.989385,0 -5.786177,0.815488 -8.183594,2.230469 l -3.074218,-4.56836 A 1.5,1.5 0 0 0 11.787109,15.027344 1.5,1.5 0 0 0 11.494141,15 Z"/>
|
|
||||||
<path style="fill:#077063" d="M 6,5 C 4.892,5 4,5.892 4,7 V 33 H 44 V 7 C 44,5.892 43.108,5 42,5 Z"/>
|
|
||||||
<path style="opacity:0.1;fill:#ffffff" d="M 6,5 C 4.892,5 4,5.892 4,7 V 8 C 4,6.892 4.892,6 6,6 h 36 c 1.108,0 2,0.892 2,2 V 7 C 44,5.892 43.108,5 42,5 Z"/>
|
|
||||||
<path style="fill:none;stroke:#30dd81;stroke-width:3;stroke-linecap:round" d="M 15.1998,21.000026 11.5,15.5"/>
|
|
||||||
<path style="fill:none;stroke:#30dd81;stroke-width:3;stroke-linecap:round" d="M 32.799764,21.000026 36.5,15.5"/>
|
|
||||||
<path style="fill:#30dd81" d="m 24.002386,17.000034 c -8.355868,0 -15.2214979,6.346843 -15.9999669,14.460906 C 7.9289259,33.000882 9.5544999,33 9.5544999,33 H 38.406003 c 0,0 1.672201,-3.35e-4 1.591142,-1.617185 C 39.182807,23.305596 32.331836,17.000034 24.002386,17.000034 Z"/>
|
|
||||||
<path style="opacity:0.2" d="m 16,25 a 1.9999959,1.9999959 0 0 0 -2,2 1.9999959,1.9999959 0 0 0 2,2 1.9999959,1.9999959 0 0 0 2,-2 1.9999959,1.9999959 0 0 0 -2,-2 z m 16,0 a 1.9999959,1.9999959 0 0 0 -2,2 1.9999959,1.9999959 0 0 0 2,2 1.9999959,1.9999959 0 0 0 2,-2 1.9999959,1.9999959 0 0 0 -2,-2 z"/>
|
|
||||||
<path style="fill:#ffffff" d="M 15.999996,24.000008 A 1.9999959,1.9999959 0 0 1 17.999992,26.000004 1.9999959,1.9999959 0 0 1 15.999996,28 1.9999959,1.9999959 0 0 1 14,26.000004 1.9999959,1.9999959 0 0 1 15.999996,24.000008 Z"/>
|
|
||||||
<path style="fill:#ffffff" d="M 31.999996,24.000008 A 1.9999959,1.9999959 0 0 1 33.999991,26.000004 1.9999959,1.9999959 0 0 1 31.999996,28 1.9999959,1.9999959 0 0 1 30,26.000004 1.9999959,1.9999959 0 0 1 31.999996,24.000008 Z"/>
|
|
||||||
<path style="fill:#ffffff;opacity:0.2" d="M 11.494141 14 A 1.5 1.5 0 0 0 10.662109 14.255859 A 1.5 1.5 0 0 0 10.115234 16.001953 A 1.5 1.5 0 0 1 10.662109 15.255859 A 1.5 1.5 0 0 1 11.494141 15 A 1.5 1.5 0 0 1 11.787109 15.027344 A 1.5 1.5 0 0 1 12.744141 15.662109 L 15.818359 20.230469 C 18.215776 18.815488 21.012568 18 24.001953 18 C 26.990024 18 29.78649 18.814887 32.183594 20.228516 L 35.255859 15.662109 A 1.5 1.5 0 0 1 36.212891 15.027344 A 1.5 1.5 0 0 1 37.337891 15.255859 A 1.5 1.5 0 0 1 37.910156 16.001953 A 1.5 1.5 0 0 0 37.337891 14.255859 A 1.5 1.5 0 0 0 36.212891 14.027344 A 1.5 1.5 0 0 0 35.255859 14.662109 L 32.183594 19.228516 C 29.78649 17.814887 26.990024 17 24.001953 17 C 21.012568 17 18.215776 17.815488 15.818359 19.230469 L 12.744141 14.662109 A 1.5 1.5 0 0 0 11.787109 14.027344 A 1.5 1.5 0 0 0 11.494141 14 z M 35.033203 21.369141 L 34.617188 21.988281 C 37.477056 24.493668 39.433905 27.993356 39.943359 31.945312 C 39.986866 31.783283 40.008864 31.598575 39.998047 31.382812 C 39.601372 27.448291 37.768055 23.938648 35.033203 21.369141 z M 12.970703 21.373047 C 10.220358 23.959215 8.3822757 27.496796 8.0019531 31.460938 C 7.9920657 31.668114 8.0150508 31.844846 8.0585938 32 C 8.5570234 28.027243 10.515755 24.509049 13.386719 21.992188 L 12.970703 21.373047 z"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 4.5 KiB |
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@ -94,7 +94,6 @@ dist-win32: build-server build-win32
|
|||||||
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)"
|
cp data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)"
|
||||||
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)"
|
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)"
|
||||||
cp data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)"
|
|
||||||
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||||
@ -111,7 +110,6 @@ dist-win64: build-server build-win64
|
|||||||
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)"
|
cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)"
|
||||||
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
|
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
|
||||||
cp data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)"
|
|
||||||
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||||
|
4
run
4
run
@ -20,6 +20,4 @@ then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SCRCPY_ICON_PATH="data/icon.png" \
|
SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" "$BUILDDIR/app/scrcpy" "$@"
|
||||||
SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" \
|
|
||||||
"$BUILDDIR/app/scrcpy" "$@"
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 31
|
compileSdkVersion 30
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.genymobile.scrcpy"
|
applicationId "com.genymobile.scrcpy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 31
|
targetSdkVersion 30
|
||||||
versionCode 11900
|
versionCode 11900
|
||||||
versionName "1.19"
|
versionName "1.19"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
@ -2,6 +2,7 @@ package com.genymobile.scrcpy;
|
|||||||
|
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user