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 |
13
BUILD.md
13
BUILD.md
@ -14,8 +14,7 @@ First, you need to install the required packages:
|
||||
# for Debian/Ubuntu
|
||||
sudo apt install ffmpeg libsdl2-2.0-0 adb wget \
|
||||
gcc git pkg-config meson ninja-build libsdl2-dev \
|
||||
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
|
||||
libusb-1.0-0 libusb-dev
|
||||
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev
|
||||
```
|
||||
|
||||
Then clone the repo and execute the installation script
|
||||
@ -93,7 +92,7 @@ sudo apt install ffmpeg libsdl2-2.0-0 adb libusb-1.0-0
|
||||
|
||||
# client build dependencies
|
||||
sudo apt install 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-dev
|
||||
|
||||
# server build dependencies
|
||||
@ -161,7 +160,8 @@ install the required packages:
|
||||
```bash
|
||||
# runtime dependencies
|
||||
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
|
||||
pacman -S mingw-w64-x86_64-make \
|
||||
@ -175,7 +175,8 @@ For a 32 bits version, replace `x86_64` by `i686`:
|
||||
```bash
|
||||
# runtime dependencies
|
||||
pacman -S mingw-w64-i686-SDL2 \
|
||||
mingw-w64-i686-ffmpeg
|
||||
mingw-w64-i686-ffmpeg \
|
||||
mingw-w64-i686-libusb
|
||||
|
||||
# client build dependencies
|
||||
pacman -S mingw-w64-i686-make \
|
||||
@ -199,7 +200,7 @@ Install the packages with [Homebrew]:
|
||||
|
||||
```bash
|
||||
# runtime dependencies
|
||||
brew install sdl2 ffmpeg
|
||||
brew install sdl2 ffmpeg libusb
|
||||
|
||||
# client build dependencies
|
||||
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
|
||||
|
||||
|
||||
### 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
|
||||
|
||||
|
12
FAQ.md
12
FAQ.md
@ -118,17 +118,13 @@ In developer options, enable:
|
||||
|
||||
### Special characters do not work
|
||||
|
||||
The default text injection method is [limited to ASCII characters][text-input].
|
||||
A trick allows to also inject some [accented characters][accented-characters],
|
||||
but that's all. See [#37].
|
||||
|
||||
Since scrcpy v1.20 on Linux, it is possible to simulate a [physical
|
||||
keyboard][hid] (HID).
|
||||
Injecting text input is [limited to ASCII characters][text-input]. A trick
|
||||
allows to also inject some [accented characters][accented-characters], but
|
||||
that's all. See [#37].
|
||||
|
||||
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
|
||||
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
|
||||
[#37]: https://github.com/Genymobile/scrcpy/issues/37
|
||||
[hid]: README.md#physical-keyboard-simulation-hid
|
||||
|
||||
|
||||
## Client issues
|
||||
@ -262,6 +258,6 @@ to add some arguments.
|
||||
|
||||
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)
|
||||
- [简体中文 (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._
|
||||
|
||||
# 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_.
|
||||
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:
|
||||
|
||||
```bash
|
||||
scrcpy --lock-video-orientation 0 # orientamento naturale
|
||||
scrcpy --lock-video-orientation 1 # 90° antiorario
|
||||
scrcpy --lock-video-orientation 2 # 180°
|
||||
scrcpy --lock-video-orientation 3 # 90° orario
|
||||
scrcpy --lock-video-orientation # orientamento iniziale (corrente)
|
||||
scrcpy --lock-video-orientation=0 # orientamento naturale
|
||||
scrcpy --lock-video-orientation=1 # 90° antiorario
|
||||
scrcpy --lock-video-orientation=2 # 180°
|
||||
scrcpy --lock-video-orientation=3 # 90° orario
|
||||
```
|
||||
|
||||
Questo influisce sull'orientamento della registrazione.
|
||||
@ -231,7 +232,9 @@ Per elencare i codificatori disponibili puoi immettere un nome di codificatore n
|
||||
scrcpy --encoder _
|
||||
```
|
||||
|
||||
### Registrazione
|
||||
### Cattura
|
||||
|
||||
#### Registrazione
|
||||
|
||||
È 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
|
||||
|
||||
|
||||
#### 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
|
||||
|
||||
#### Wireless
|
||||
@ -479,15 +551,6 @@ scrcpy --turn-screen-off --stay-awake
|
||||
scrcpy -Sw
|
||||
```
|
||||
|
||||
#### Renderizzare i fotogrammi scaduti
|
||||
|
||||
Per minimizzare la latenza _scrcpy_ renderizza sempre l'ultimo fotogramma decodificato disponibile in maniera predefinita e tralascia quelli precedenti.
|
||||
|
||||
Per forzare la renderizzazione di tutti i fotogrammi (a costo di una possibile latenza superiore), utilizzare:
|
||||
|
||||
```bash
|
||||
scrcpy --render-expired-frames
|
||||
```
|
||||
|
||||
#### Mostrare i tocchi
|
||||
|
||||
@ -607,14 +670,14 @@ Non c'è alcuna risposta visiva, un log è stampato nella console.
|
||||
|
||||
#### Trasferimento di file verso il dispositivo
|
||||
|
||||
Per trasferire un file in `/sdcard/` del dispositivo trascina e rilascia un file (non APK) nella finestra di _scrcpy_.
|
||||
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.
|
||||
|
||||
La cartella di destinazione può essere cambiata all'avvio:
|
||||
|
||||
```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 destra | <kbd>MOD</kbd>+<kbd>→</kbd> _(destra)_
|
||||
| Ridimensiona finestra a 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
|
||||
| Ridimensiona la finestra per rimuovere i bordi neri | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Doppio click¹_
|
||||
| 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 `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 `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(su)_
|
||||
| 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>
|
||||
| Accendi lo schermo del dispositivo | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
||||
| Ruota lo schermo del dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
|
||||
| Espandi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>n</kbd>
|
||||
| Chiudi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||
| Copia negli appunti³ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||
| Taglia negli appunti³ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||
| Sincronizza gli appunti e incolla³ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||
| Espandi il pannello delle notifiche | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5° click³_
|
||||
| Espandi il pannello delle impostazioni | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Doppio 5° click³_
|
||||
| Chiudi pannelli | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||
| Copia negli appunti⁴ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||
| Taglia negli appunti⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||
| Sincronizza gli appunti e incolla⁴ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||
| Inietta il testo degli appunti del computer | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
||||
| Abilita/Disabilita il contatore FPS (su stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||
| Pizzica per zoomare | <kbd>Ctrl</kbd>+_click e trascina_
|
||||
|
||||
_¹Doppio click sui bordi neri per rimuoverli._
|
||||
_²Il tasto destro accende lo schermo se era spento, preme BACK in caso contrario._
|
||||
_³Solo in Android >= 7._
|
||||
_³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.
|
||||
|
||||
|
74
README.md
74
README.md
@ -1,7 +1,5 @@
|
||||
# scrcpy (v1.19)
|
||||
|
||||

|
||||
|
||||
[Read in another language](#translations)
|
||||
|
||||
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.
|
||||
|
||||
#### 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
|
||||
|
||||
@ -326,7 +347,9 @@ For example, you could capture the video within [OBS].
|
||||
#### Buffering
|
||||
|
||||
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:
|
||||
|
||||
@ -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
|
||||
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
|
||||
|
||||
@ -725,9 +715,6 @@ scrcpy --prefer-text
|
||||
|
||||
(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
|
||||
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
||||
|
||||
@ -743,9 +730,6 @@ To avoid forwarding repeated key events:
|
||||
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
|
||||
|
||||
@ -867,7 +851,7 @@ ADB=/path/to/adb scrcpy
|
||||
To override the path of the `scrcpy-server` file, configure its path in
|
||||
`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_?
|
||||
@ -927,10 +911,10 @@ Read the [developers page].
|
||||
This README is available in other languages:
|
||||
|
||||
- [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)
|
||||
- [한국어 (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)
|
||||
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.17](README.zh-Hans.md)
|
||||
- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md)
|
||||
|
174
README.pt-br.md
174
README.pt-br.md
@ -1,6 +1,6 @@
|
||||
_Apenas o [README](README.md) original é garantido estar atualizado._
|
||||
|
||||
# scrcpy (v1.17)
|
||||
# scrcpy (v1.19)
|
||||
|
||||
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_.
|
||||
@ -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>
|
||||
|
||||
### 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
|
||||
|
||||
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-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
|
||||
difícil).
|
||||
|
||||
Você também pode [compilar o app manualmente][BUILD] ([processo simplificado][BUILD_simple]).
|
||||
|
||||
|
||||
### Windows
|
||||
@ -113,13 +123,18 @@ brew install scrcpy
|
||||
Você precisa do `adb`, acessível pelo seu `PATH`. Se você ainda não o tem:
|
||||
|
||||
```bash
|
||||
# Homebrew >= 2.6.0
|
||||
brew install --cask android-platform-tools
|
||||
|
||||
# Homebrew < 2.6.0
|
||||
brew cask install android-platform-tools
|
||||
brew 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].
|
||||
|
||||
|
||||
@ -195,10 +210,11 @@ Se `--max-size` também for especificado, o redimensionamento é aplicado após
|
||||
Para travar a orientação do espelhamento:
|
||||
|
||||
```bash
|
||||
scrcpy --lock-video-orientation 0 # orientação natural
|
||||
scrcpy --lock-video-orientation 1 # 90° sentido anti-horário
|
||||
scrcpy --lock-video-orientation 2 # 180°
|
||||
scrcpy --lock-video-orientation 3 # 90° sentido horário
|
||||
scrcpy --lock-video-orientation # orientação inicial (Atual)
|
||||
scrcpy --lock-video-orientation=0 # orientação natural
|
||||
scrcpy --lock-video-orientation=1 # 90° sentido anti-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.
|
||||
@ -222,7 +238,9 @@ erro dará os encoders disponíveis:
|
||||
scrcpy --encoder _
|
||||
```
|
||||
|
||||
### Gravando
|
||||
### Captura
|
||||
|
||||
#### Gravando
|
||||
|
||||
É 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
|
||||
|
||||
|
||||
#### 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
|
||||
|
||||
#### 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
|
||||
|
||||
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
|
||||
|
||||
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_.
|
||||
|
||||
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>
|
||||
| Rotacionar display para esquerda | <kbd>MOD</kbd>+<kbd>←</kbd> _(esquerda)_
|
||||
| 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 remover bordas pretas | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Clique-duplo¹_
|
||||
| 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-esquerdo¹_
|
||||
| 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 `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd>
|
||||
| Clicar em `MENU` (desbloquear tela | <kbd>MOD</kbd>+<kbd>m</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 `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(cima)_
|
||||
| Clicar em `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(baixo)_
|
||||
| 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>
|
||||
| Ligar tela do dispositivo | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
|
||||
| Rotacionar tela do dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
|
||||
| Expandir painel de notificação | <kbd>MOD</kbd>+<kbd>n</kbd>
|
||||
| Colapsar painel de notificação | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||
| Copiar para área de transferência³ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||
| Recortar para área de transferência³ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||
| Sincronizar áreas de transferência e colar³ | <kbd>MOD</kbd>+<kbd>v</kbd>
|
||||
| Expandir painel de notificação | <kbd>MOD</kbd>+<kbd>n</kbd> \| _Clique-do-5.°³_
|
||||
| Expandir painel de configurção | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Clique-duplo-do-5.°³_
|
||||
| Colapsar paineis | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
|
||||
| Copiar para área de transferência⁴ | <kbd>MOD</kbd>+<kbd>c</kbd>
|
||||
| Recortar para área de transferência⁴ | <kbd>MOD</kbd>+<kbd>x</kbd>
|
||||
| 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>
|
||||
| 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-direito liga a tela se ela estiver desligada, pressiona BACK caso contrário._
|
||||
_³Apenas em Android >= 7._
|
||||
_¹Clique-duplo-esquerdo na borda preta para remove-la._
|
||||
_²Clique-direito liga a tela caso esteja desligada, pressione BACK caso contrário._
|
||||
_³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
|
||||
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
|
||||
`ADB`:
|
||||
|
||||
ADB=/caminho/para/adb scrcpy
|
||||
```bash
|
||||
ADB=/caminho/para/adb scrcpy
|
||||
```
|
||||
|
||||
Para sobrepor o caminho do arquivo `scrcpy-server`, configure seu caminho em
|
||||
`SCRCPY_SERVER_PATH`.
|
||||
@ -751,8 +841,6 @@ Um colega me desafiou a encontrar um nome tão impronunciável quanto [gnirehtet
|
||||
|
||||
Veja [BUILD].
|
||||
|
||||
[BUILD]: BUILD.md
|
||||
|
||||
|
||||
## Problemas comuns
|
||||
|
||||
|
@ -8,7 +8,6 @@ src = [
|
||||
'src/controller.c',
|
||||
'src/decoder.c',
|
||||
'src/device_msg.c',
|
||||
'src/icon.c',
|
||||
'src/file_handler.c',
|
||||
'src/fps_counter.c',
|
||||
'src/frame_buffer.c',
|
||||
@ -16,13 +15,13 @@ src = [
|
||||
'src/keyboard_inject.c',
|
||||
'src/mouse_inject.c',
|
||||
'src/opengl.c',
|
||||
'src/options.c',
|
||||
'src/receiver.c',
|
||||
'src/recorder.c',
|
||||
'src/scrcpy.c',
|
||||
'src/screen.c',
|
||||
'src/server.c',
|
||||
'src/stream.c',
|
||||
'src/tiny_xpm.c',
|
||||
'src/video_buffer.c',
|
||||
'src/util/log.c',
|
||||
'src/util/net.c',
|
||||
@ -166,9 +165,6 @@ executable('scrcpy', src,
|
||||
c_args: [])
|
||||
|
||||
install_man('scrcpy.1')
|
||||
install_data('../data/icon.png',
|
||||
rename: 'scrcpy.png',
|
||||
install_dir: 'share/icons/hicolor/256x256/apps')
|
||||
|
||||
|
||||
### TESTS
|
||||
@ -185,7 +181,6 @@ if get_option('buildtype') == 'debug'
|
||||
['test_cli', [
|
||||
'tests/test_cli.c',
|
||||
'src/cli.c',
|
||||
'src/options.c',
|
||||
'src/util/str_util.c',
|
||||
]],
|
||||
['test_clock', [
|
||||
|
12
app/scrcpy.1
12
app/scrcpy.1
@ -83,13 +83,23 @@ Start in fullscreen.
|
||||
Print this help.
|
||||
|
||||
.TP
|
||||
.B \-K, \-\-hid\-keyboard
|
||||
.B \-K, \-\-keyboard\-hid
|
||||
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.
|
||||
|
||||
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
|
||||
.B \-\-legacy\-paste
|
||||
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
|
||||
|
||||
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...
|
||||
assert(event->size);
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
void
|
||||
sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id,
|
||||
unsigned char *buffer, uint16_t buffer_size) {
|
||||
hid_event->accessory_id = accessory_id;
|
||||
hid_event->buffer = buffer;
|
||||
hid_event->size = buffer_size;
|
||||
hid_event->delay = 0;
|
||||
static void
|
||||
hid_event_destroy(struct hid_event *event) {
|
||||
free(event->buffer);
|
||||
}
|
||||
|
||||
void
|
||||
sc_hid_event_destroy(struct sc_hid_event *hid_event) {
|
||||
free(hid_event->buffer);
|
||||
}
|
||||
|
||||
static inline void
|
||||
static void
|
||||
log_libusb_error(enum libusb_error errcode) {
|
||||
LOGW("libusb error: %s", libusb_strerror(errcode));
|
||||
}
|
||||
@ -82,7 +73,7 @@ accept_device(libusb_device *device, const char *serial) {
|
||||
}
|
||||
|
||||
static libusb_device *
|
||||
sc_aoa_find_usb_device(const char *serial) {
|
||||
aoa_find_usb_device(const char *serial) {
|
||||
if (!serial) {
|
||||
return NULL;
|
||||
}
|
||||
@ -108,7 +99,7 @@ sc_aoa_find_usb_device(const char *serial) {
|
||||
}
|
||||
|
||||
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);
|
||||
if (result < 0) {
|
||||
log_libusb_error((enum libusb_error) result);
|
||||
@ -118,7 +109,7 @@ sc_aoa_open_usb_handle(libusb_device *device, libusb_device_handle **handle) {
|
||||
}
|
||||
|
||||
bool
|
||||
sc_aoa_init(struct sc_aoa *aoa, const char *serial) {
|
||||
aoa_init(struct aoa *aoa, const char *serial) {
|
||||
cbuf_init(&aoa->queue);
|
||||
|
||||
if (!sc_mutex_init(&aoa->mutex)) {
|
||||
@ -136,7 +127,7 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aoa->usb_device = sc_aoa_find_usb_device(serial);
|
||||
aoa->usb_device = aoa_find_usb_device(serial);
|
||||
if (!aoa->usb_device) {
|
||||
LOGW("USB device of serial %s not found", serial);
|
||||
libusb_exit(aoa->usb_context);
|
||||
@ -145,7 +136,7 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial) {
|
||||
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");
|
||||
libusb_unref_device(aoa->usb_device);
|
||||
libusb_exit(aoa->usb_context);
|
||||
@ -160,11 +151,11 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial) {
|
||||
}
|
||||
|
||||
void
|
||||
sc_aoa_destroy(struct sc_aoa *aoa) {
|
||||
aoa_destroy(struct aoa *aoa) {
|
||||
// Destroy remaining events
|
||||
struct sc_hid_event event;
|
||||
struct hid_event event;
|
||||
while (cbuf_take(&aoa->queue, &event)) {
|
||||
sc_hid_event_destroy(&event);
|
||||
hid_event_destroy(&event);
|
||||
}
|
||||
|
||||
libusb_close(aoa->usb_handle);
|
||||
@ -175,7 +166,7 @@ sc_aoa_destroy(struct sc_aoa *aoa) {
|
||||
}
|
||||
|
||||
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) {
|
||||
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
|
||||
uint8_t request = ACCESSORY_REGISTER_HID;
|
||||
@ -198,7 +189,7 @@ sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id,
|
||||
}
|
||||
|
||||
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,
|
||||
uint16_t report_desc_size) {
|
||||
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
|
||||
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) {
|
||||
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) {
|
||||
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);
|
||||
if (!ok) {
|
||||
if (!sc_aoa_unregister_hid(aoa, accessory_id)) {
|
||||
if (!aoa_unregister_hid(aoa, accessory_id)) {
|
||||
LOGW("Could not unregister HID");
|
||||
}
|
||||
return false;
|
||||
@ -254,13 +245,13 @@ sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
|
||||
}
|
||||
|
||||
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 = ACCESSORY_SEND_HID_EVENT;
|
||||
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
|
||||
// value (arg0): accessory assigned ID for the HID device
|
||||
// index (arg1): 0 (unused)
|
||||
uint16_t value = event->accessory_id;
|
||||
uint16_t value = event->from_accessory_id;
|
||||
uint16_t index = 0;
|
||||
unsigned char *buffer = event->buffer;
|
||||
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
|
||||
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 = ACCESSORY_UNREGISTER_HID;
|
||||
// <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
|
||||
sc_aoa_push_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
|
||||
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
|
||||
sc_hid_event_log(event);
|
||||
}
|
||||
|
||||
aoa_push_hid_event(struct aoa *aoa, const struct hid_event *event) {
|
||||
hid_event_log(event);
|
||||
sc_mutex_lock(&aoa->mutex);
|
||||
bool was_empty = cbuf_is_empty(&aoa->queue);
|
||||
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
|
||||
run_aoa_thread(void *data) {
|
||||
struct sc_aoa *aoa = data;
|
||||
struct aoa *aoa = data;
|
||||
|
||||
for (;;) {
|
||||
sc_mutex_lock(&aoa->mutex);
|
||||
@ -327,30 +315,14 @@ run_aoa_thread(void *data) {
|
||||
sc_mutex_unlock(&aoa->mutex);
|
||||
break;
|
||||
}
|
||||
struct sc_hid_event event;
|
||||
struct hid_event event;
|
||||
bool non_empty = cbuf_take(&aoa->queue, &event);
|
||||
assert(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);
|
||||
|
||||
bool ok = sc_aoa_send_hid_event(aoa, &event);
|
||||
sc_hid_event_destroy(&event);
|
||||
bool ok = aoa_send_hid_event(aoa, &event);
|
||||
hid_event_destroy(&event);
|
||||
if (!ok) {
|
||||
LOGW("Could not send HID event to USB device");
|
||||
}
|
||||
@ -359,7 +331,7 @@ run_aoa_thread(void *data) {
|
||||
}
|
||||
|
||||
bool
|
||||
sc_aoa_start(struct sc_aoa *aoa) {
|
||||
aoa_start(struct aoa *aoa) {
|
||||
LOGD("Starting AOA thread");
|
||||
|
||||
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
|
||||
sc_aoa_stop(struct sc_aoa *aoa) {
|
||||
aoa_stop(struct aoa *aoa) {
|
||||
sc_mutex_lock(&aoa->mutex);
|
||||
aoa->stopped = true;
|
||||
sc_cond_signal(&aoa->event_cond);
|
||||
@ -380,6 +352,6 @@ sc_aoa_stop(struct sc_aoa *aoa) {
|
||||
}
|
||||
|
||||
void
|
||||
sc_aoa_join(struct sc_aoa *aoa) {
|
||||
aoa_join(struct aoa *aoa) {
|
||||
sc_thread_join(&aoa->thread, NULL);
|
||||
}
|
||||
|
@ -1,33 +1,24 @@
|
||||
#ifndef SC_AOA_HID_H
|
||||
#define SC_AOA_HID_H
|
||||
#ifndef AOA_HID_H
|
||||
#define AOA_HID_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <libusb-1.0/libusb.h>
|
||||
|
||||
#include "scrcpy.h"
|
||||
#include "util/cbuf.h"
|
||||
#include "util/thread.h"
|
||||
#include "util/tick.h"
|
||||
|
||||
struct sc_hid_event {
|
||||
uint16_t accessory_id;
|
||||
struct hid_event {
|
||||
uint16_t from_accessory_id;
|
||||
unsigned char *buffer;
|
||||
uint16_t size;
|
||||
sc_tick delay;
|
||||
};
|
||||
|
||||
// Takes ownership of buffer
|
||||
void
|
||||
sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id,
|
||||
unsigned char *buffer, uint16_t buffer_size);
|
||||
struct hid_event_queue CBUF(struct hid_event, 64);
|
||||
|
||||
void
|
||||
sc_hid_event_destroy(struct sc_hid_event *hid_event);
|
||||
|
||||
struct sc_hid_event_queue CBUF(struct sc_hid_event, 64);
|
||||
|
||||
struct sc_aoa {
|
||||
struct aoa {
|
||||
libusb_context *usb_context;
|
||||
libusb_device *usb_device;
|
||||
libusb_device_handle *usb_handle;
|
||||
@ -35,32 +26,32 @@ struct sc_aoa {
|
||||
sc_mutex mutex;
|
||||
sc_cond event_cond;
|
||||
bool stopped;
|
||||
struct sc_hid_event_queue queue;
|
||||
struct hid_event_queue queue;
|
||||
};
|
||||
|
||||
bool
|
||||
sc_aoa_init(struct sc_aoa *aoa, const char *serial);
|
||||
aoa_init(struct aoa *aoa, const char *serial);
|
||||
|
||||
void
|
||||
sc_aoa_destroy(struct sc_aoa *aoa);
|
||||
aoa_destroy(struct aoa *aoa);
|
||||
|
||||
bool
|
||||
sc_aoa_start(struct sc_aoa *aoa);
|
||||
aoa_start(struct aoa *aoa);
|
||||
|
||||
void
|
||||
sc_aoa_stop(struct sc_aoa *aoa);
|
||||
aoa_stop(struct aoa *aoa);
|
||||
|
||||
void
|
||||
sc_aoa_join(struct sc_aoa *aoa);
|
||||
aoa_join(struct aoa *aoa);
|
||||
|
||||
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);
|
||||
|
||||
bool
|
||||
sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id);
|
||||
aoa_unregister_hid(struct aoa *aoa, uint16_t accessory_id);
|
||||
|
||||
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
|
||||
|
@ -6,7 +6,7 @@
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "options.h"
|
||||
#include "scrcpy.h"
|
||||
#include "util/log.h"
|
||||
#include "util/str_util.h"
|
||||
|
||||
@ -76,7 +76,7 @@ scrcpy_print_usage(const char *arg0) {
|
||||
" -f, --fullscreen\n"
|
||||
" Start in fullscreen.\n"
|
||||
"\n"
|
||||
" -K, --hid-keyboard\n"
|
||||
" -K, --keyboard-hid\n"
|
||||
" Simulate a physical keyboard by using HID over AOAv2.\n"
|
||||
" It provides a better experience for IME users, and allows to\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},
|
||||
{"fullscreen", no_argument, NULL, 'f'},
|
||||
{"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},
|
||||
{"lock-video-orientation", optional_argument, NULL,
|
||||
OPT_LOCK_VIDEO_ORIENTATION},
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "options.h"
|
||||
#include "scrcpy.h"
|
||||
|
||||
struct scrcpy_cli_args {
|
||||
struct scrcpy_options opts;
|
||||
|
@ -26,7 +26,7 @@ struct sc_clock_point {
|
||||
* array.
|
||||
*
|
||||
* 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
|
||||
* passing through these two points.
|
||||
*
|
||||
|
@ -56,7 +56,7 @@ static const char *const screen_power_mode_labels[] = {
|
||||
};
|
||||
|
||||
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[4], position->point.y);
|
||||
buffer_write16be(&buf[8], position->screen_size.width);
|
||||
|
@ -57,11 +57,11 @@ struct control_msg {
|
||||
enum android_motionevent_action action;
|
||||
enum android_motionevent_buttons buttons;
|
||||
uint64_t pointer_id;
|
||||
struct sc_position position;
|
||||
struct position position;
|
||||
float pressure;
|
||||
} inject_touch_event;
|
||||
struct {
|
||||
struct sc_position position;
|
||||
struct position position;
|
||||
int32_t hscroll;
|
||||
int32_t vscroll;
|
||||
} inject_scroll_event;
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include "util/log.h"
|
||||
|
||||
bool
|
||||
controller_init(struct controller *controller, sc_socket control_socket) {
|
||||
controller_init(struct controller *controller, socket_t control_socket) {
|
||||
cbuf_init(&controller->queue);
|
||||
|
||||
bool ok = receiver_init(&controller->receiver, control_socket);
|
||||
@ -63,14 +63,14 @@ controller_push_msg(struct controller *controller,
|
||||
}
|
||||
|
||||
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];
|
||||
size_t length = control_msg_serialize(msg, serialized_msg);
|
||||
if (!length) {
|
||||
return false;
|
||||
}
|
||||
ssize_t w =
|
||||
net_send_all(controller->control_socket, serialized_msg, length);
|
||||
ssize_t w = net_send_all(controller->control_socket, serialized_msg, length);
|
||||
return (size_t) w == length;
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
struct control_msg_queue CBUF(struct control_msg, 64);
|
||||
|
||||
struct controller {
|
||||
sc_socket control_socket;
|
||||
socket_t control_socket;
|
||||
sc_thread thread;
|
||||
sc_mutex mutex;
|
||||
sc_cond msg_cond;
|
||||
@ -24,7 +24,7 @@ struct controller {
|
||||
};
|
||||
|
||||
bool
|
||||
controller_init(struct controller *controller, sc_socket control_socket);
|
||||
controller_init(struct controller *controller, socket_t control_socket);
|
||||
|
||||
void
|
||||
controller_destroy(struct controller *controller);
|
||||
|
@ -3,22 +3,22 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct sc_size {
|
||||
struct size {
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
};
|
||||
|
||||
struct sc_point {
|
||||
struct point {
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
};
|
||||
|
||||
struct sc_position {
|
||||
struct position {
|
||||
// The video screen size may be different from the real device screen size,
|
||||
// so store to which size the absolute position apply, to scale it
|
||||
// accordingly.
|
||||
struct sc_size screen_size;
|
||||
struct sc_point point;
|
||||
struct size screen_size;
|
||||
struct point point;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -1,5 +1,2 @@
|
||||
#define EVENT_NEW_FRAME SDL_USEREVENT
|
||||
#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"
|
||||
|
||||
/** 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_MODIFIER_NONE 0x00
|
||||
#define HID_MODIFIER_LEFT_CONTROL (1 << 0)
|
||||
#define HID_MODIFIER_LEFT_SHIFT (1 << 1)
|
||||
#define HID_MODIFIER_LEFT_ALT (1 << 2)
|
||||
#define HID_MODIFIER_LEFT_GUI (1 << 3)
|
||||
#define HID_MODIFIER_RIGHT_CONTROL (1 << 4)
|
||||
#define HID_MODIFIER_RIGHT_SHIFT (1 << 5)
|
||||
#define HID_MODIFIER_RIGHT_ALT (1 << 6)
|
||||
#define HID_MODIFIER_RIGHT_GUI (1 << 7)
|
||||
#define HID_KEYBOARD_MODIFIER_NONE 0x00
|
||||
#define HID_KEYBOARD_MODIFIER_LEFT_CONTROL (1 << 0)
|
||||
#define HID_KEYBOARD_MODIFIER_LEFT_SHIFT (1 << 1)
|
||||
#define HID_KEYBOARD_MODIFIER_LEFT_ALT (1 << 2)
|
||||
#define HID_KEYBOARD_MODIFIER_LEFT_GUI (1 << 3)
|
||||
#define HID_KEYBOARD_MODIFIER_RIGHT_CONTROL (1 << 4)
|
||||
#define HID_KEYBOARD_MODIFIER_RIGHT_SHIFT (1 << 5)
|
||||
#define HID_KEYBOARD_MODIFIER_RIGHT_ALT (1 << 6)
|
||||
#define HID_KEYBOARD_MODIFIER_RIGHT_GUI (1 << 7)
|
||||
|
||||
#define HID_KEYBOARD_INDEX_MODIFIER 0
|
||||
#define HID_KEYBOARD_INDEX_KEYS 2
|
||||
@ -29,8 +30,11 @@
|
||||
#define HID_KEYBOARD_MAX_KEYS 6
|
||||
#define HID_KEYBOARD_EVENT_SIZE (2 + HID_KEYBOARD_MAX_KEYS)
|
||||
|
||||
#define HID_RESERVED 0x00
|
||||
#define HID_ERROR_ROLL_OVER 0x01
|
||||
#define HID_KEYBOARD_RESERVED 0x00
|
||||
#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.
|
||||
@ -110,11 +114,11 @@ static const unsigned char keyboard_report_desc[] = {
|
||||
// Usage Minimum (0)
|
||||
0x19, 0x00,
|
||||
// Usage Maximum (101)
|
||||
0x29, SC_HID_KEYBOARD_KEYS - 1,
|
||||
0x29, HID_KEYBOARD_KEYS - 1,
|
||||
// Logical Minimum (0)
|
||||
0x15, 0x00,
|
||||
// Logical Maximum(101)
|
||||
0x25, SC_HID_KEYBOARD_KEYS - 1,
|
||||
0x25, HID_KEYBOARD_KEYS - 1,
|
||||
// Report Size (8)
|
||||
0x75, 0x08,
|
||||
// Report Count (6)
|
||||
@ -126,49 +130,129 @@ static const unsigned char keyboard_report_desc[] = {
|
||||
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
|
||||
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) {
|
||||
modifiers |= HID_MODIFIER_LEFT_CONTROL;
|
||||
modifiers |= HID_KEYBOARD_MODIFIER_LEFT_CONTROL;
|
||||
}
|
||||
if (mod & KMOD_LSHIFT) {
|
||||
modifiers |= HID_MODIFIER_LEFT_SHIFT;
|
||||
modifiers |= HID_KEYBOARD_MODIFIER_LEFT_SHIFT;
|
||||
}
|
||||
if (mod & KMOD_LALT) {
|
||||
modifiers |= HID_MODIFIER_LEFT_ALT;
|
||||
modifiers |= HID_KEYBOARD_MODIFIER_LEFT_ALT;
|
||||
}
|
||||
if (mod & KMOD_LGUI) {
|
||||
modifiers |= HID_MODIFIER_LEFT_GUI;
|
||||
modifiers |= HID_KEYBOARD_MODIFIER_LEFT_GUI;
|
||||
}
|
||||
if (mod & KMOD_RCTRL) {
|
||||
modifiers |= HID_MODIFIER_RIGHT_CONTROL;
|
||||
modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_CONTROL;
|
||||
}
|
||||
if (mod & KMOD_RSHIFT) {
|
||||
modifiers |= HID_MODIFIER_RIGHT_SHIFT;
|
||||
modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_SHIFT;
|
||||
}
|
||||
if (mod & KMOD_RALT) {
|
||||
modifiers |= HID_MODIFIER_RIGHT_ALT;
|
||||
modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_ALT;
|
||||
}
|
||||
if (mod & KMOD_RGUI) {
|
||||
modifiers |= HID_MODIFIER_RIGHT_GUI;
|
||||
modifiers |= HID_KEYBOARD_MODIFIER_RIGHT_GUI;
|
||||
}
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
|
||||
unsigned char *buffer = malloc(HID_KEYBOARD_EVENT_SIZE);
|
||||
if (!buffer) {
|
||||
return false;
|
||||
send_mod_lock_state(struct hid_keyboard *kb, unsigned lock_mod) {
|
||||
assert(!(lock_mod & ~SC_MOD_MASK));
|
||||
if (!lock_mod) {
|
||||
// Nothing to do
|
||||
return true;
|
||||
}
|
||||
|
||||
buffer[HID_KEYBOARD_INDEX_MODIFIER] = HID_MODIFIER_NONE;
|
||||
buffer[1] = HID_RESERVED;
|
||||
memset(&buffer[HID_KEYBOARD_INDEX_KEYS], 0, HID_KEYBOARD_MAX_KEYS);
|
||||
struct hid_event hid_event;
|
||||
hid_event.from_accessory_id = HID_KEYBOARD_ACCESSORY_ID;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -178,102 +262,28 @@ scancode_is_modifier(SDL_Scancode scancode) {
|
||||
}
|
||||
|
||||
static bool
|
||||
convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
|
||||
struct sc_hid_event *hid_event,
|
||||
hid_keyboard_convert_event(struct hid_keyboard *kb, struct hid_event *hid_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;
|
||||
assert(scancode >= 0);
|
||||
|
||||
// SDL also generates events when only modifiers are pressed, we cannot
|
||||
// ignore them totally, for example press 'a' first then press 'Control',
|
||||
// if we ignore 'Control' event, only 'a' is sent.
|
||||
if (scancode >= SC_HID_KEYBOARD_KEYS && !scancode_is_modifier(scancode)) {
|
||||
// Scancode to ignore
|
||||
if (scancode < HID_KEYBOARD_KEYS || scancode_is_modifier(scancode)) {
|
||||
return convert_hid_keyboard_event(kb, hid_event, event);
|
||||
}
|
||||
|
||||
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
|
||||
@ -285,33 +295,12 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||
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
|
||||
if (convert_hid_keyboard_event(kb, &hid_event, event)) {
|
||||
if (!kb->mod_lock_synchronized) {
|
||||
// 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);
|
||||
if (hid_keyboard_convert_event(kb, &hid_event, event)) {
|
||||
if (!aoa_push_hid_event(kb->aoa, &hid_event)) {
|
||||
LOGW("Could request HID event");
|
||||
}
|
||||
}
|
||||
@ -327,21 +316,21 @@ sc_key_processor_process_text(struct sc_key_processor *kp,
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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,
|
||||
ARRAY_LEN(keyboard_report_desc));
|
||||
if (!ok) {
|
||||
LOGW("Register HID keyboard failed");
|
||||
LOGW("Register HID for keyboard failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset all states
|
||||
memset(kb->keys, false, SC_HID_KEYBOARD_KEYS);
|
||||
|
||||
kb->mod_lock_synchronized = false;
|
||||
memset(kb->keys, false, HID_KEYBOARD_KEYS);
|
||||
|
||||
static const struct sc_key_processor_ops ops = {
|
||||
.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;
|
||||
|
||||
// FIXME to avoid pipe error
|
||||
usleep(100000);
|
||||
send_mod_lock_state(kb, lock_mod);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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
|
||||
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) {
|
||||
LOGW("Could not unregister HID keyboard");
|
||||
LOGW("Could not unregister HID");
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
#ifndef SC_HID_KEYBOARD_H
|
||||
#define SC_HID_KEYBOARD_H
|
||||
#ifndef HID_KEYBOARD_H
|
||||
#define HID_KEYBOARD_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "aoa_hid.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
|
||||
// HID protocol.
|
||||
// 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
|
||||
@ -26,19 +32,17 @@
|
||||
* phantom state. Don't forget that modifiers should be updated too, even for
|
||||
* phantom state.
|
||||
*/
|
||||
struct sc_hid_keyboard {
|
||||
struct hid_keyboard {
|
||||
struct sc_key_processor key_processor; // key processor trait
|
||||
|
||||
struct sc_aoa *aoa;
|
||||
bool keys[SC_HID_KEYBOARD_KEYS];
|
||||
|
||||
bool mod_lock_synchronized;
|
||||
struct aoa *aoa;
|
||||
bool keys[HID_KEYBOARD_KEYS];
|
||||
};
|
||||
|
||||
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
|
||||
sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb);
|
||||
hid_keyboard_destroy(struct hid_keyboard *kb);
|
||||
|
||||
#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
|
||||
simulate_virtual_finger(struct input_manager *im,
|
||||
enum android_motionevent_action action,
|
||||
struct sc_point point) {
|
||||
struct point point) {
|
||||
bool up = action == AMOTION_EVENT_ACTION_UP;
|
||||
|
||||
struct control_msg msg;
|
||||
@ -352,8 +352,8 @@ simulate_virtual_finger(struct input_manager *im,
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct sc_point
|
||||
inverse_point(struct sc_point point, struct sc_size size) {
|
||||
static struct point
|
||||
inverse_point(struct point point, struct size size) {
|
||||
point.x = size.width - point.x;
|
||||
point.y = size.height - point.y;
|
||||
return point;
|
||||
@ -376,6 +376,8 @@ input_manager_process_key(struct input_manager *im,
|
||||
|
||||
bool smod = is_shortcut_mod(im, mod);
|
||||
|
||||
LOGD("=== %x", (int) mod);
|
||||
|
||||
if (down && !repeat) {
|
||||
if (keycode == im->last_keycode && mod == im->last_mod) {
|
||||
++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);
|
||||
|
||||
if (im->vfinger_down) {
|
||||
struct sc_point mouse =
|
||||
struct point mouse =
|
||||
screen_convert_window_to_frame_coords(im->screen, event->x,
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -630,10 +632,10 @@ input_manager_process_mouse_button(struct input_manager *im,
|
||||
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
|
||||
if ((down && !im->vfinger_down && CTRL_PRESSED)
|
||||
|| (!down && im->vfinger_down)) {
|
||||
struct sc_point mouse =
|
||||
struct point mouse =
|
||||
screen_convert_window_to_frame_coords(im->screen, event->x,
|
||||
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
|
||||
? AMOTION_EVENT_ACTION_DOWN
|
||||
: AMOTION_EVENT_ACTION_UP;
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
#include "controller.h"
|
||||
#include "fps_counter.h"
|
||||
#include "options.h"
|
||||
#include "scrcpy.h"
|
||||
#include "screen.h"
|
||||
#include "trait/key_processor.h"
|
||||
#include "trait/mouse_processor.h"
|
||||
|
@ -6,7 +6,7 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "controller.h"
|
||||
#include "options.h"
|
||||
#include "scrcpy.h"
|
||||
#include "trait/key_processor.h"
|
||||
|
||||
struct sc_keyboard_inject {
|
||||
|
@ -1,3 +1,5 @@
|
||||
#include "scrcpy.h"
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <assert.h>
|
||||
@ -11,8 +13,6 @@
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "cli.h"
|
||||
#include "options.h"
|
||||
#include "scrcpy.h"
|
||||
#include "util/log.h"
|
||||
|
||||
static void
|
||||
@ -48,7 +48,7 @@ main(int argc, char *argv[]) {
|
||||
#endif
|
||||
|
||||
struct scrcpy_cli_args args = {
|
||||
.opts = scrcpy_options_default,
|
||||
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||
.help = false,
|
||||
.version = false,
|
||||
};
|
||||
|
@ -125,7 +125,7 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
|
||||
int mouse_y;
|
||||
SDL_GetMouseState(&mouse_x, &mouse_y);
|
||||
|
||||
struct sc_position position = {
|
||||
struct position position = {
|
||||
.screen_size = screen->frame_size,
|
||||
.point = screen_convert_window_to_frame_coords(screen,
|
||||
mouse_x, mouse_y),
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "controller.h"
|
||||
#include "scrcpy.h"
|
||||
#include "screen.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"
|
||||
|
||||
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);
|
||||
if (!ok) {
|
||||
return false;
|
||||
|
@ -11,13 +11,13 @@
|
||||
// receive events from the device
|
||||
// managed by the controller
|
||||
struct receiver {
|
||||
sc_socket control_socket;
|
||||
socket_t control_socket;
|
||||
sc_thread thread;
|
||||
sc_mutex mutex;
|
||||
};
|
||||
|
||||
bool
|
||||
receiver_init(struct receiver *receiver, sc_socket control_socket);
|
||||
receiver_init(struct receiver *receiver, socket_t control_socket);
|
||||
|
||||
void
|
||||
receiver_destroy(struct receiver *receiver);
|
||||
|
@ -372,7 +372,7 @@ bool
|
||||
recorder_init(struct recorder *recorder,
|
||||
const char *filename,
|
||||
enum sc_record_format format,
|
||||
struct sc_size declared_frame_size) {
|
||||
struct size declared_frame_size) {
|
||||
recorder->filename = strdup(filename);
|
||||
if (!recorder->filename) {
|
||||
LOGE("Could not strdup filename");
|
||||
|
@ -7,7 +7,7 @@
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include "coords.h"
|
||||
#include "options.h"
|
||||
#include "scrcpy.h"
|
||||
#include "trait/packet_sink.h"
|
||||
#include "util/queue.h"
|
||||
#include "util/thread.h"
|
||||
@ -25,7 +25,7 @@ struct recorder {
|
||||
char *filename;
|
||||
enum sc_record_format format;
|
||||
AVFormatContext *ctx;
|
||||
struct sc_size declared_frame_size;
|
||||
struct size declared_frame_size;
|
||||
bool header_written;
|
||||
|
||||
sc_thread thread;
|
||||
@ -44,7 +44,7 @@ struct recorder {
|
||||
|
||||
bool
|
||||
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
|
||||
recorder_destroy(struct recorder *recorder);
|
||||
|
151
app/src/scrcpy.c
151
app/src/scrcpy.c
@ -27,6 +27,7 @@
|
||||
#include "screen.h"
|
||||
#include "server.h"
|
||||
#include "stream.h"
|
||||
#include "tiny_xpm.h"
|
||||
#include "util/log.h"
|
||||
#include "util/net.h"
|
||||
#ifdef HAVE_V4L2
|
||||
@ -45,34 +46,24 @@ struct scrcpy {
|
||||
struct controller controller;
|
||||
struct file_handler file_handler;
|
||||
#ifdef HAVE_AOA_HID
|
||||
struct sc_aoa aoa;
|
||||
struct aoa aoa;
|
||||
#endif
|
||||
union {
|
||||
struct sc_keyboard_inject keyboard_inject;
|
||||
#ifdef HAVE_AOA_HID
|
||||
struct sc_hid_keyboard keyboard_hid;
|
||||
struct hid_keyboard keyboard_hid;
|
||||
#endif
|
||||
};
|
||||
struct sc_mouse_inject mouse_inject;
|
||||
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
|
||||
BOOL WINAPI windows_ctrl_handler(DWORD ctrl_type) {
|
||||
if (ctrl_type == CTRL_C_EVENT) {
|
||||
PUSH_EVENT(SDL_QUIT);
|
||||
SDL_Event event;
|
||||
event.type = SDL_QUIT;
|
||||
SDL_PushEvent(&event);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
@ -158,10 +149,6 @@ static enum event_result
|
||||
handle_event(struct scrcpy *s, const struct scrcpy_options *options,
|
||||
SDL_Event *event) {
|
||||
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:
|
||||
LOGD("Video stream stopped");
|
||||
return EVENT_RESULT_STOPPED_BY_EOS;
|
||||
@ -220,32 +207,6 @@ event_loop(struct scrcpy *s, const struct scrcpy_options *options) {
|
||||
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
|
||||
sdl_priority_from_av_level(int level) {
|
||||
switch (level) {
|
||||
@ -288,31 +249,21 @@ stream_on_eos(struct stream *stream, void *userdata) {
|
||||
(void) stream;
|
||||
(void) userdata;
|
||||
|
||||
PUSH_EVENT(EVENT_STREAM_STOPPED);
|
||||
SDL_Event stop_event;
|
||||
stop_event.type = EVENT_STREAM_STOPPED;
|
||||
SDL_PushEvent(&stop_event);
|
||||
}
|
||||
|
||||
static void
|
||||
server_on_connection_failed(struct server *server, void *userdata) {
|
||||
(void) server;
|
||||
(void) userdata;
|
||||
|
||||
PUSH_EVENT(EVENT_SERVER_CONNECTION_FAILED);
|
||||
}
|
||||
|
||||
static void
|
||||
server_on_connected(struct server *server, void *userdata) {
|
||||
(void) server;
|
||||
(void) userdata;
|
||||
|
||||
PUSH_EVENT(EVENT_SERVER_CONNECTED);
|
||||
}
|
||||
|
||||
static void
|
||||
server_on_disconnected(struct server *server, void *userdata) {
|
||||
(void) server;
|
||||
(void) userdata;
|
||||
|
||||
PUSH_EVENT(EVENT_SERVER_DISCONNECTED);
|
||||
static unsigned
|
||||
to_sc_mod(SDL_Keymod sdl_mod) {
|
||||
unsigned mod = 0;
|
||||
if (sdl_mod & KMOD_CAPS) {
|
||||
mod |= SC_MOD_CAPSLOCK;
|
||||
}
|
||||
if (sdl_mod & KMOD_NUM) {
|
||||
mod |= SC_MOD_NUMLOCK;
|
||||
}
|
||||
return mod;
|
||||
}
|
||||
|
||||
bool
|
||||
@ -320,6 +271,10 @@ scrcpy(struct scrcpy_options *options) {
|
||||
static struct scrcpy scrcpy;
|
||||
struct scrcpy *s = &scrcpy;
|
||||
|
||||
if (!server_init(&s->server)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
|
||||
bool server_started = false;
|
||||
@ -355,18 +310,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
.force_adb_forward = options->force_adb_forward,
|
||||
.power_off_on_close = options->power_off_on_close,
|
||||
};
|
||||
|
||||
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)) {
|
||||
if (!server_start(&s->server, ¶ms)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
@ -377,15 +321,15 @@ scrcpy(struct scrcpy_options *options) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
// Await for server without blocking Ctrl+C handling
|
||||
if (!await_for_server()) {
|
||||
char device_name[DEVICE_NAME_FIELD_LENGTH];
|
||||
struct size frame_size;
|
||||
|
||||
if (!server_connect_to(&s->server, device_name, &frame_size)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
struct server_info *info = &s->server.info;
|
||||
|
||||
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)) {
|
||||
goto end;
|
||||
}
|
||||
@ -407,7 +351,7 @@ scrcpy(struct scrcpy_options *options) {
|
||||
if (!recorder_init(&s->recorder,
|
||||
options->record_filename,
|
||||
options->record_format,
|
||||
info->frame_size)) {
|
||||
frame_size)) {
|
||||
goto end;
|
||||
}
|
||||
rec = &s->recorder;
|
||||
@ -453,11 +397,11 @@ scrcpy(struct scrcpy_options *options) {
|
||||
|
||||
if (options->display) {
|
||||
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 = {
|
||||
.window_title = window_title,
|
||||
.frame_size = info->frame_size,
|
||||
.frame_size = frame_size,
|
||||
.always_on_top = options->always_on_top,
|
||||
.window_x = options->window_x,
|
||||
.window_y = options->window_y,
|
||||
@ -480,8 +424,8 @@ scrcpy(struct scrcpy_options *options) {
|
||||
|
||||
#ifdef HAVE_V4L2
|
||||
if (options->v4l2_device) {
|
||||
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device,
|
||||
info->frame_size, options->v4l2_buffer)) {
|
||||
if (!sc_v4l2_sink_init(&s->v4l2_sink, options->v4l2_device, frame_size,
|
||||
options->v4l2_buffer)) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
@ -519,20 +463,23 @@ scrcpy(struct scrcpy_options *options) {
|
||||
LOGI("Device serial: %s", serial);
|
||||
}
|
||||
|
||||
bool ok = sc_aoa_init(&s->aoa, serial);
|
||||
bool ok = aoa_init(&s->aoa, serial);
|
||||
free(serialno);
|
||||
if (!ok) {
|
||||
goto aoa_hid_end;
|
||||
}
|
||||
|
||||
if (!sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) {
|
||||
sc_aoa_destroy(&s->aoa);
|
||||
if (!aoa_start(&s->aoa)) {
|
||||
aoa_destroy(&s->aoa);
|
||||
goto aoa_hid_end;
|
||||
}
|
||||
|
||||
if (!sc_aoa_start(&s->aoa)) {
|
||||
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
||||
sc_aoa_destroy(&s->aoa);
|
||||
// FIXME: SDL_GetModState() always returns 0 here :/
|
||||
unsigned mod = to_sc_mod(SDL_GetModState());
|
||||
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;
|
||||
}
|
||||
|
||||
@ -545,13 +492,13 @@ aoa_hid_end:
|
||||
if (!aoa_hid_ok) {
|
||||
LOGE("Failed to enable HID over AOA, "
|
||||
"fallback to default keyboard injection method "
|
||||
"(-K/--hid-keyboard ignored)");
|
||||
"(-K/--keyboard-hid ignored)");
|
||||
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
|
||||
}
|
||||
#else
|
||||
LOGE("HID over AOA is not supported on this platform, "
|
||||
"fallback to default keyboard injection method "
|
||||
"(-K/--hid-keyboard ignored)");
|
||||
"(-K/--keyboard-hid ignored)");
|
||||
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
|
||||
#endif
|
||||
}
|
||||
@ -582,8 +529,8 @@ end:
|
||||
// end-of-stream
|
||||
#ifdef HAVE_AOA_HID
|
||||
if (aoa_hid_initialized) {
|
||||
sc_hid_keyboard_destroy(&s->keyboard_hid);
|
||||
sc_aoa_stop(&s->aoa);
|
||||
hid_keyboard_destroy(&s->keyboard_hid);
|
||||
aoa_stop(&s->aoa);
|
||||
}
|
||||
#endif
|
||||
if (controller_started) {
|
||||
@ -615,8 +562,8 @@ end:
|
||||
|
||||
#ifdef HAVE_AOA_HID
|
||||
if (aoa_hid_initialized) {
|
||||
sc_aoa_join(&s->aoa);
|
||||
sc_aoa_destroy(&s->aoa);
|
||||
aoa_join(&s->aoa);
|
||||
aoa_destroy(&s->aoa);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
153
app/src/scrcpy.h
153
app/src/scrcpy.h
@ -4,7 +4,158 @@
|
||||
#include "common.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
|
||||
scrcpy(struct scrcpy_options *options);
|
||||
|
110
app/src/screen.c
110
app/src/screen.c
@ -5,8 +5,9 @@
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "events.h"
|
||||
#include "icon.h"
|
||||
#include "options.h"
|
||||
#include "icon.xpm"
|
||||
#include "scrcpy.h"
|
||||
#include "tiny_xpm.h"
|
||||
#include "video_buffer.h"
|
||||
#include "util/log.h"
|
||||
|
||||
@ -14,9 +15,9 @@
|
||||
|
||||
#define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink)
|
||||
|
||||
static inline struct sc_size
|
||||
get_rotated_size(struct sc_size size, int rotation) {
|
||||
struct sc_size rotated_size;
|
||||
static inline struct size
|
||||
get_rotated_size(struct size size, int rotation) {
|
||||
struct size rotated_size;
|
||||
if (rotation & 1) {
|
||||
rotated_size.width = size.height;
|
||||
rotated_size.height = size.width;
|
||||
@ -27,26 +28,26 @@ get_rotated_size(struct sc_size size, int rotation) {
|
||||
return rotated_size;
|
||||
}
|
||||
|
||||
// get the window size in a struct sc_size
|
||||
static struct sc_size
|
||||
// get the window size in a struct size
|
||||
static struct size
|
||||
get_window_size(const struct screen *screen) {
|
||||
int width;
|
||||
int height;
|
||||
SDL_GetWindowSize(screen->window, &width, &height);
|
||||
|
||||
struct sc_size size;
|
||||
struct size size;
|
||||
size.width = width;
|
||||
size.height = height;
|
||||
return size;
|
||||
}
|
||||
|
||||
static struct sc_point
|
||||
static struct point
|
||||
get_window_position(const struct screen *screen) {
|
||||
int x;
|
||||
int y;
|
||||
SDL_GetWindowPosition(screen->window, &x, &y);
|
||||
|
||||
struct sc_point point;
|
||||
struct point point;
|
||||
point.x = x;
|
||||
point.y = y;
|
||||
return point;
|
||||
@ -54,7 +55,7 @@ get_window_position(const struct screen *screen) {
|
||||
|
||||
// set the window size to be applied when fullscreen is disabled
|
||||
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->maximized);
|
||||
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)
|
||||
static bool
|
||||
get_preferred_display_bounds(struct sc_size *bounds) {
|
||||
get_preferred_display_bounds(struct size *bounds) {
|
||||
SDL_Rect rect;
|
||||
#ifdef SCRCPY_SDL_HAS_GET_DISPLAY_USABLE_BOUNDS
|
||||
# define GET_DISPLAY_BOUNDS(i, r) SDL_GetDisplayUsableBounds((i), (r))
|
||||
@ -80,7 +81,7 @@ get_preferred_display_bounds(struct sc_size *bounds) {
|
||||
}
|
||||
|
||||
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
|
||||
// size from the other
|
||||
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)
|
||||
// - it keeps the aspect ratio
|
||||
// - it scales down to make it fit in the display_size
|
||||
static struct sc_size
|
||||
get_optimal_size(struct sc_size current_size, struct sc_size content_size) {
|
||||
static struct size
|
||||
get_optimal_size(struct size current_size, struct size content_size) {
|
||||
if (content_size.width == 0 || content_size.height == 0) {
|
||||
// avoid division by 0
|
||||
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)) {
|
||||
// could not get display bounds, do not constraint the size
|
||||
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
|
||||
// req_width and req_height, if not 0, are the sizes requested by the user
|
||||
static inline struct sc_size
|
||||
get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
|
||||
static inline struct size
|
||||
get_initial_optimal_size(struct size content_size, uint16_t req_width,
|
||||
uint16_t req_height) {
|
||||
struct sc_size window_size;
|
||||
struct size window_size;
|
||||
if (!req_width && !req_height) {
|
||||
window_size = get_optimal_size(content_size, content_size);
|
||||
} else {
|
||||
@ -166,9 +167,9 @@ screen_update_content_rect(struct screen *screen) {
|
||||
int 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
|
||||
struct sc_size drawable_size = {dw, dh};
|
||||
struct size drawable_size = {dw, dh};
|
||||
|
||||
SDL_Rect *rect = &screen->rect;
|
||||
|
||||
@ -200,7 +201,7 @@ screen_update_content_rect(struct screen *screen) {
|
||||
static inline SDL_Texture *
|
||||
create_texture(struct screen *screen) {
|
||||
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_TEXTUREACCESS_STREAMING,
|
||||
size.width, size.height);
|
||||
@ -282,33 +283,17 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
|
||||
(void) vb;
|
||||
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) {
|
||||
fps_counter_add_skipped_frame(&screen->fps_counter);
|
||||
// The EVENT_NEW_FRAME triggered for the previous frame will consume
|
||||
// this new frame instead, unless the previous event failed
|
||||
need_new_event = screen->event_failed;
|
||||
// this new frame instead
|
||||
} else {
|
||||
need_new_event = true;
|
||||
}
|
||||
|
||||
if (need_new_event) {
|
||||
static SDL_Event new_frame_event = {
|
||||
.type = EVENT_NEW_FRAME,
|
||||
};
|
||||
|
||||
// Post the event on the UI thread
|
||||
int ret = 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;
|
||||
}
|
||||
SDL_PushEvent(&new_frame_event);
|
||||
}
|
||||
}
|
||||
|
||||
@ -318,7 +303,6 @@ screen_init(struct screen *screen, const struct screen_params *params) {
|
||||
screen->has_frame = false;
|
||||
screen->fullscreen = false;
|
||||
screen->maximized = false;
|
||||
screen->event_failed = false;
|
||||
|
||||
static const struct sc_video_buffer_callbacks cbs = {
|
||||
.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) {
|
||||
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);
|
||||
screen->content_size = content_size;
|
||||
|
||||
struct sc_size window_size =
|
||||
get_initial_optimal_size(content_size,params->window_width,
|
||||
struct size window_size = get_initial_optimal_size(content_size,
|
||||
params->window_width,
|
||||
params->window_height);
|
||||
uint32_t window_flags = SDL_WINDOW_HIDDEN
|
||||
| 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)");
|
||||
}
|
||||
|
||||
SDL_Surface *icon = scrcpy_icon_load();
|
||||
SDL_Surface *icon = read_xpm(icon_xpm);
|
||||
if (icon) {
|
||||
SDL_SetWindowIcon(screen->window, icon);
|
||||
scrcpy_icon_destroy(icon);
|
||||
SDL_FreeSurface(icon);
|
||||
} else {
|
||||
LOGW("Could not load icon");
|
||||
}
|
||||
@ -525,10 +509,10 @@ screen_destroy(struct screen *screen) {
|
||||
}
|
||||
|
||||
static void
|
||||
resize_for_content(struct screen *screen, struct sc_size old_content_size,
|
||||
struct sc_size new_content_size) {
|
||||
struct sc_size window_size = get_window_size(screen);
|
||||
struct sc_size target_size = {
|
||||
resize_for_content(struct screen *screen, struct size old_content_size,
|
||||
struct size new_content_size) {
|
||||
struct size window_size = get_window_size(screen);
|
||||
struct size target_size = {
|
||||
.width = (uint32_t) window_size.width * new_content_size.width
|
||||
/ old_content_size.width,
|
||||
.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
|
||||
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) {
|
||||
resize_for_content(screen, screen->content_size, new_content_size);
|
||||
} else if (!screen->resize_pending) {
|
||||
@ -570,7 +554,7 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_size new_content_size =
|
||||
struct size new_content_size =
|
||||
get_rotated_size(screen->frame_size, rotation);
|
||||
|
||||
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
|
||||
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
|
||||
|| screen->frame_size.height != new_frame_size.height) {
|
||||
// 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;
|
||||
|
||||
struct sc_size new_content_size =
|
||||
struct size new_content_size =
|
||||
get_rotated_size(new_frame_size, screen->rotation);
|
||||
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);
|
||||
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
@ -699,10 +683,10 @@ screen_resize_to_fit(struct screen *screen) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_point point = get_window_position(screen);
|
||||
struct sc_size window_size = get_window_size(screen);
|
||||
struct point point = get_window_position(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);
|
||||
|
||||
// Center the window related to the device screen
|
||||
@ -728,7 +712,7 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
|
||||
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);
|
||||
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
|
||||
content_size.height);
|
||||
@ -783,7 +767,7 @@ screen_handle_event(struct screen *screen, SDL_Event *event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sc_point
|
||||
struct point
|
||||
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
||||
int32_t x, int32_t y) {
|
||||
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;
|
||||
|
||||
// rotate
|
||||
struct sc_point result;
|
||||
struct point result;
|
||||
switch (rotation) {
|
||||
case 0:
|
||||
result.x = x;
|
||||
@ -820,7 +804,7 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
|
||||
return result;
|
||||
}
|
||||
|
||||
struct sc_point
|
||||
struct point
|
||||
screen_convert_window_to_frame_coords(struct screen *screen,
|
||||
int32_t x, int32_t y) {
|
||||
screen_hidpi_scale_coords(screen, &x, &y);
|
||||
|
@ -27,13 +27,13 @@ struct screen {
|
||||
SDL_Renderer *renderer;
|
||||
SDL_Texture *texture;
|
||||
struct sc_opengl gl;
|
||||
struct sc_size frame_size;
|
||||
struct sc_size content_size; // rotated frame_size
|
||||
struct size frame_size;
|
||||
struct size content_size; // rotated frame_size
|
||||
|
||||
bool resize_pending; // resize requested while fullscreen or maximized
|
||||
// The content size the last time the window was not maximized or
|
||||
// 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)
|
||||
unsigned rotation;
|
||||
@ -44,14 +44,12 @@ struct screen {
|
||||
bool maximized;
|
||||
bool mipmaps;
|
||||
|
||||
bool event_failed; // in case SDL_PushEvent() returned an error
|
||||
|
||||
AVFrame *frame;
|
||||
};
|
||||
|
||||
struct screen_params {
|
||||
const char *window_title;
|
||||
struct sc_size frame_size;
|
||||
struct size frame_size;
|
||||
bool always_on_top;
|
||||
|
||||
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
|
||||
// x and y are expressed in pixels
|
||||
struct sc_point
|
||||
struct point
|
||||
screen_convert_window_to_frame_coords(struct screen *screen,
|
||||
int32_t x, int32_t y);
|
||||
|
||||
// convert point from drawable coordinates to frame coordinates
|
||||
// x and y are expressed in pixels
|
||||
struct sc_point
|
||||
struct point
|
||||
screen_convert_drawable_to_frame_coords(struct screen *screen,
|
||||
int32_t x, int32_t y);
|
||||
|
||||
|
358
app/src/server.c
358
app/src/server.c
@ -3,6 +3,7 @@
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <libgen.h>
|
||||
#include <stdio.h>
|
||||
#include <SDL2/SDL_timer.h>
|
||||
#include <SDL2/SDL_platform.h>
|
||||
@ -47,56 +48,54 @@ get_server_path(void) {
|
||||
LOGE("Could not allocate memory");
|
||||
return NULL;
|
||||
}
|
||||
// the absolute path is hardcoded
|
||||
return server_path;
|
||||
#else
|
||||
char *server_path = get_local_file_path(SERVER_FILENAME);
|
||||
if (!server_path) {
|
||||
LOGE("Could not get local file path, "
|
||||
|
||||
// use scrcpy-server in the same directory as the executable
|
||||
char *executable_path = get_executable_path();
|
||||
if (!executable_path) {
|
||||
LOGE("Could not get executable path, "
|
||||
"using " SERVER_FILENAME " from current directory");
|
||||
// not found, use current directory
|
||||
return strdup(SERVER_FILENAME);
|
||||
}
|
||||
|
||||
LOGD("Using server (portable): %s", server_path);
|
||||
#endif
|
||||
|
||||
return server_path;
|
||||
}
|
||||
|
||||
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; \
|
||||
} \
|
||||
// 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);
|
||||
}
|
||||
|
||||
COPY(serial);
|
||||
COPY(crop);
|
||||
COPY(codec_options);
|
||||
COPY(encoder_name);
|
||||
#undef COPY
|
||||
*p = '\0'; // modify executable_path in place
|
||||
char *dir = executable_path;
|
||||
size_t dirlen = strlen(dir);
|
||||
|
||||
return true;
|
||||
// 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);
|
||||
}
|
||||
|
||||
error:
|
||||
server_params_destroy(dst);
|
||||
return false;
|
||||
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);
|
||||
return server_path;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool
|
||||
@ -141,14 +140,13 @@ disable_tunnel_forward(const char *serial, uint16_t local_port) {
|
||||
|
||||
static bool
|
||||
disable_tunnel(struct server *server) {
|
||||
const char *serial = server->params.serial;
|
||||
if (server->tunnel_forward) {
|
||||
return disable_tunnel_forward(serial, server->local_port);
|
||||
return disable_tunnel_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) {
|
||||
#define IPV4_LOCALHOST 0x7F000001
|
||||
return net_listen(IPV4_LOCALHOST, port, 1);
|
||||
@ -157,10 +155,9 @@ listen_on_port(uint16_t port) {
|
||||
static bool
|
||||
enable_tunnel_reverse_any_port(struct server *server,
|
||||
struct sc_port_range port_range) {
|
||||
const char *serial = server->params.serial;
|
||||
uint16_t port = port_range.first;
|
||||
for (;;) {
|
||||
if (!enable_tunnel_reverse(serial, port)) {
|
||||
if (!enable_tunnel_reverse(server->serial, port)) {
|
||||
// the command itself failed, it will fail on any port
|
||||
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
|
||||
// device.
|
||||
server->server_socket = listen_on_port(port);
|
||||
if (server->server_socket != SC_INVALID_SOCKET) {
|
||||
if (server->server_socket != INVALID_SOCKET) {
|
||||
// success
|
||||
server->local_port = port;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
@ -205,11 +202,9 @@ static bool
|
||||
enable_tunnel_forward_any_port(struct server *server,
|
||||
struct sc_port_range port_range) {
|
||||
server->tunnel_forward = true;
|
||||
|
||||
const char *serial = server->params.serial;
|
||||
uint16_t port = port_range.first;
|
||||
for (;;) {
|
||||
if (enable_tunnel_forward(serial, port)) {
|
||||
if (enable_tunnel_forward(server->serial, port)) {
|
||||
// success
|
||||
server->local_port = port;
|
||||
return true;
|
||||
@ -271,8 +266,6 @@ log_level_to_server_string(enum sc_log_level level) {
|
||||
|
||||
static process_t
|
||||
execute_server(struct server *server, const struct server_params *params) {
|
||||
const char *serial = server->params.serial;
|
||||
|
||||
char max_size_string[6];
|
||||
char bit_rate_string[11];
|
||||
char max_fps_string[6];
|
||||
@ -330,14 +323,14 @@ execute_server(struct server *server, const struct server_params *params) {
|
||||
// Port: 5005
|
||||
// Then click on "Debug"
|
||||
#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) {
|
||||
sc_socket socket = net_connect(IPV4_LOCALHOST, port);
|
||||
if (socket == SC_INVALID_SOCKET) {
|
||||
return SC_INVALID_SOCKET;
|
||||
socket_t socket = net_connect(IPV4_LOCALHOST, port);
|
||||
if (socket == INVALID_SOCKET) {
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
char byte;
|
||||
@ -346,127 +339,72 @@ connect_and_read_byte(uint16_t port) {
|
||||
if (net_recv(socket, &byte, 1) != 1) {
|
||||
// the server is not listening yet behind the adb tunnel
|
||||
net_close(socket);
|
||||
return SC_INVALID_SOCKET;
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
return socket;
|
||||
}
|
||||
|
||||
static sc_socket
|
||||
static socket_t
|
||||
connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
|
||||
do {
|
||||
LOGD("Remaining connection attempts: %d", (int) attempts);
|
||||
sc_socket socket = connect_and_read_byte(port);
|
||||
if (socket != SC_INVALID_SOCKET) {
|
||||
socket_t socket = connect_and_read_byte(port);
|
||||
if (socket != INVALID_SOCKET) {
|
||||
// it worked!
|
||||
return socket;
|
||||
}
|
||||
// TODO use mutex + condvar + bool stopped
|
||||
if (attempts) {
|
||||
SDL_Delay(delay);
|
||||
}
|
||||
} 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
|
||||
server_init(struct server *server, const struct server_params *params,
|
||||
const struct server_callbacks *cbs, void *cbs_userdata) {
|
||||
bool ok = server_params_copy(&server->params, params);
|
||||
if (!ok) {
|
||||
LOGE("Could not copy server params");
|
||||
return false;
|
||||
}
|
||||
|
||||
server_init(struct server *server) {
|
||||
server->serial = NULL;
|
||||
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) {
|
||||
server_params_destroy(&server->params);
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_cond_init(&server->process_terminated_cond);
|
||||
if (!ok) {
|
||||
sc_mutex_destroy(&server->mutex);
|
||||
server_params_destroy(&server->params);
|
||||
return false;
|
||||
}
|
||||
|
||||
server->process_terminated = false;
|
||||
server->connected = false;
|
||||
|
||||
server->server_socket = SC_INVALID_SOCKET;
|
||||
server->video_socket = SC_INVALID_SOCKET;
|
||||
server->control_socket = SC_INVALID_SOCKET;
|
||||
server->server_socket = INVALID_SOCKET;
|
||||
server->video_socket = INVALID_SOCKET;
|
||||
server->control_socket = INVALID_SOCKET;
|
||||
|
||||
server->local_port = 0;
|
||||
|
||||
server->tunnel_enabled = false;
|
||||
server->tunnel_forward = false;
|
||||
|
||||
assert(cbs);
|
||||
assert(cbs->on_connection_failed);
|
||||
assert(cbs->on_connected);
|
||||
assert(cbs->on_disconnected);
|
||||
|
||||
server->cbs = cbs;
|
||||
server->cbs_userdata = cbs_userdata;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int
|
||||
run_server_connect(void *data) {
|
||||
run_wait_server(void *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
|
||||
goto end;
|
||||
}
|
||||
|
||||
process_wait(server->process, false); // ignore exit code
|
||||
LOGD("Server terminated");
|
||||
|
||||
sc_mutex_lock(&server->mutex);
|
||||
server->process_terminated = true;
|
||||
@ -475,31 +413,75 @@ run_server(void *data) {
|
||||
|
||||
// no need for synchronization, server_socket is initialized before this
|
||||
// thread was created
|
||||
if (server->server_socket != SC_INVALID_SOCKET) {
|
||||
// Unblock any accept()
|
||||
net_interrupt(server->server_socket);
|
||||
if (server->server_socket != INVALID_SOCKET
|
||||
&& !atomic_flag_test_and_set(&server->server_socket_closed)) {
|
||||
// On Linux, accept() is unblocked by shutdown(), but on Windows, it is
|
||||
// unblocked by closesocket(). Therefore, call both (close_socket()).
|
||||
close_socket(server->server_socket);
|
||||
}
|
||||
|
||||
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:
|
||||
LOGD("Server terminated");
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
server_start(struct server *server) {
|
||||
return sc_thread_create(&server->thread, run_server, "server", server);
|
||||
server_start(struct server *server, const struct server_params *params) {
|
||||
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
|
||||
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];
|
||||
ssize_t r = net_recv_all(device_socket, buf, sizeof(buf));
|
||||
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
|
||||
buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0';
|
||||
memcpy(info->device_name, (char *) buf, sizeof(info->device_name));
|
||||
|
||||
info->frame_size.width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8)
|
||||
// strcpy is safe here, since name contains at least
|
||||
// DEVICE_NAME_FIELD_LENGTH bytes and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
|
||||
strcpy(device_name, (char *) buf);
|
||||
size->width = (buf[DEVICE_NAME_FIELD_LENGTH] << 8)
|
||||
| buf[DEVICE_NAME_FIELD_LENGTH + 1];
|
||||
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];
|
||||
return true;
|
||||
}
|
||||
|
||||
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) {
|
||||
server->video_socket = net_accept(server->server_socket);
|
||||
if (server->video_socket == SC_INVALID_SOCKET) {
|
||||
if (server->video_socket == INVALID_SOCKET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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
|
||||
return false;
|
||||
}
|
||||
|
||||
// we don't need the server socket anymore
|
||||
if (!net_close(server->server_socket)) {
|
||||
LOGW("Could not close server socket on connect");
|
||||
if (!atomic_flag_test_and_set(&server->server_socket_closed)) {
|
||||
// close it from here
|
||||
close_socket(server->server_socket);
|
||||
// otherwise, it is closed by run_wait_server()
|
||||
}
|
||||
// Do not attempt to close it again on server_destroy()
|
||||
server->server_socket = SC_INVALID_SOCKET;
|
||||
} else {
|
||||
uint32_t attempts = 100;
|
||||
uint32_t delay = 100; // ms
|
||||
server->video_socket =
|
||||
connect_to_server(server->local_port, attempts, delay);
|
||||
if (server->video_socket == SC_INVALID_SOCKET) {
|
||||
if (server->video_socket == INVALID_SOCKET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// we know that the device is listening, we don't need several attempts
|
||||
server->control_socket =
|
||||
net_connect(IPV4_LOCALHOST, server->local_port);
|
||||
if (server->control_socket == SC_INVALID_SOCKET) {
|
||||
if (server->control_socket == INVALID_SOCKET) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -559,25 +542,20 @@ server_connect_to(struct server *server, struct server_info *info) {
|
||||
server->tunnel_enabled = false;
|
||||
|
||||
// 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
|
||||
server_stop(struct server *server) {
|
||||
if (server->server_socket != SC_INVALID_SOCKET) {
|
||||
if (!net_interrupt(server->server_socket)) {
|
||||
LOGW("Could not interrupt server socket");
|
||||
if (server->server_socket != INVALID_SOCKET
|
||||
&& !atomic_flag_test_and_set(&server->server_socket_closed)) {
|
||||
close_socket(server->server_socket);
|
||||
}
|
||||
if (server->video_socket != INVALID_SOCKET) {
|
||||
close_socket(server->video_socket);
|
||||
}
|
||||
if (server->video_socket != SC_INVALID_SOCKET) {
|
||||
if (!net_interrupt(server->video_socket)) {
|
||||
LOGW("Could not interrupt video socket");
|
||||
}
|
||||
}
|
||||
if (server->control_socket != SC_INVALID_SOCKET) {
|
||||
if (!net_interrupt(server->control_socket)) {
|
||||
LOGW("Could not interrupt control socket");
|
||||
}
|
||||
if (server->control_socket != INVALID_SOCKET) {
|
||||
close_socket(server->control_socket);
|
||||
}
|
||||
|
||||
assert(server->process != PROCESS_NONE);
|
||||
@ -608,29 +586,13 @@ server_stop(struct server *server) {
|
||||
process_terminate(server->process);
|
||||
}
|
||||
|
||||
sc_thread_join(&server->thread, NULL);
|
||||
sc_thread_join(&server->wait_server_thread, NULL);
|
||||
process_close(server->process);
|
||||
}
|
||||
|
||||
void
|
||||
server_destroy(struct server *server) {
|
||||
if (server->server_socket != SC_INVALID_SOCKET) {
|
||||
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);
|
||||
free(server->serial);
|
||||
sc_cond_destroy(&server->process_terminated_cond);
|
||||
sc_mutex_destroy(&server->mutex);
|
||||
}
|
||||
|
@ -9,15 +9,27 @@
|
||||
|
||||
#include "adb.h"
|
||||
#include "coords.h"
|
||||
#include "options.h"
|
||||
#include "scrcpy.h"
|
||||
#include "util/log.h"
|
||||
#include "util/net.h"
|
||||
#include "util/thread.h"
|
||||
|
||||
#define DEVICE_NAME_FIELD_LENGTH 64
|
||||
struct server_info {
|
||||
char device_name[DEVICE_NAME_FIELD_LENGTH];
|
||||
struct sc_size frame_size;
|
||||
struct server {
|
||||
char *serial;
|
||||
process_t process;
|
||||
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 {
|
||||
@ -39,50 +51,19 @@ struct server_params {
|
||||
bool power_off_on_close;
|
||||
};
|
||||
|
||||
struct server {
|
||||
// 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
|
||||
// init default values
|
||||
bool
|
||||
server_init(struct server *server, const struct server_params *params,
|
||||
const struct server_callbacks *cbs, void *cbs_userdata);
|
||||
server_init(struct server *server);
|
||||
|
||||
// push, enable tunnel et start the server
|
||||
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
|
||||
// device_name must point to a buffer of at least DEVICE_NAME_FIELD_LENGTH bytes
|
||||
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
|
||||
void
|
||||
|
@ -260,7 +260,7 @@ end:
|
||||
}
|
||||
|
||||
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) {
|
||||
stream->socket = socket;
|
||||
stream->pending = NULL;
|
||||
|
@ -14,7 +14,7 @@
|
||||
#define STREAM_MAX_SINKS 2
|
||||
|
||||
struct stream {
|
||||
sc_socket socket;
|
||||
socket_t socket;
|
||||
sc_thread thread;
|
||||
|
||||
struct sc_packet_sink *sinks[STREAM_MAX_SINKS];
|
||||
@ -35,7 +35,7 @@ struct stream_callbacks {
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
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 "options.h"
|
||||
#include "scrcpy.h"
|
||||
|
||||
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
||||
#define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
|
||||
|
@ -1,6 +1,5 @@
|
||||
#include "net.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <SDL2/SDL_platform.h>
|
||||
|
||||
@ -8,7 +7,6 @@
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
typedef int socklen_t;
|
||||
typedef SOCKET sc_raw_socket;
|
||||
#else
|
||||
# include <sys/types.h>
|
||||
# include <sys/socket.h>
|
||||
@ -19,9 +17,122 @@
|
||||
typedef struct sockaddr_in SOCKADDR_IN;
|
||||
typedef struct sockaddr SOCKADDR;
|
||||
typedef struct in_addr IN_ADDR;
|
||||
typedef int sc_raw_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
|
||||
}
|
||||
|
||||
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
|
||||
net_init(void) {
|
||||
#ifdef __WINDOWS__
|
||||
@ -42,189 +153,11 @@ net_cleanup(void) {
|
||||
#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
|
||||
net_interrupt(sc_socket socket) {
|
||||
assert(socket != SC_INVALID_SOCKET);
|
||||
|
||||
sc_raw_socket raw_sock = unwrap(socket);
|
||||
|
||||
net_close(socket_t socket) {
|
||||
#ifdef __WINDOWS__
|
||||
if (!atomic_flag_test_and_set(&socket->closed)) {
|
||||
return !closesocket(raw_sock);
|
||||
}
|
||||
return true;
|
||||
return !closesocket(socket);
|
||||
#else
|
||||
return !shutdown(raw_sock, SHUT_RDWR);
|
||||
#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);
|
||||
return !close(socket);
|
||||
#endif
|
||||
}
|
||||
|
@ -8,20 +8,15 @@
|
||||
#include <SDL2/SDL_platform.h>
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
|
||||
# include <winsock2.h>
|
||||
# include <stdatomic.h>
|
||||
# define SC_INVALID_SOCKET NULL
|
||||
typedef struct sc_socket_windows {
|
||||
SOCKET socket;
|
||||
atomic_flag closed;
|
||||
} *sc_socket;
|
||||
|
||||
#else // not __WINDOWS__
|
||||
|
||||
#define SHUT_RD SD_RECEIVE
|
||||
#define SHUT_WR SD_SEND
|
||||
#define SHUT_RDWR SD_BOTH
|
||||
typedef SOCKET socket_t;
|
||||
#else
|
||||
# include <sys/socket.h>
|
||||
# define SC_INVALID_SOCKET -1
|
||||
typedef int sc_socket;
|
||||
# define INVALID_SOCKET -1
|
||||
typedef int socket_t;
|
||||
#endif
|
||||
|
||||
bool
|
||||
@ -30,36 +25,33 @@ net_init(void);
|
||||
void
|
||||
net_cleanup(void);
|
||||
|
||||
sc_socket
|
||||
socket_t
|
||||
net_connect(uint32_t addr, uint16_t port);
|
||||
|
||||
sc_socket
|
||||
socket_t
|
||||
net_listen(uint32_t addr, uint16_t port, int backlog);
|
||||
|
||||
sc_socket
|
||||
net_accept(sc_socket server_socket);
|
||||
socket_t
|
||||
net_accept(socket_t server_socket);
|
||||
|
||||
// the _all versions wait/retry until len bytes have been written/read
|
||||
ssize_t
|
||||
net_recv(sc_socket socket, void *buf, size_t len);
|
||||
net_recv(socket_t socket, void *buf, size_t len);
|
||||
|
||||
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
|
||||
net_send(sc_socket socket, const void *buf, size_t len);
|
||||
net_send(socket_t socket, const void *buf, size_t len);
|
||||
|
||||
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
|
||||
// recv() are interrupted.
|
||||
// how is SHUT_RD (read), SHUT_WR (write) or SHUT_RDWR (both)
|
||||
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
|
||||
net_close(sc_socket socket);
|
||||
net_close(socket_t socket);
|
||||
|
||||
#endif
|
||||
|
@ -1,6 +1,5 @@
|
||||
#include "process.h"
|
||||
|
||||
#include <libgen.h>
|
||||
#include "log.h"
|
||||
|
||||
bool
|
||||
@ -21,47 +20,6 @@ process_check_success(process_t proc, const char *name, bool close) {
|
||||
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
|
||||
read_pipe_all(pipe_t pipe, char *data, size_t len) {
|
||||
size_t copied = 0;
|
||||
|
@ -84,11 +84,6 @@ search_executable(const char *file);
|
||||
char *
|
||||
get_executable_path(void);
|
||||
|
||||
// Return the absolute path of a file in the same directory as he executable.
|
||||
// May be NULL on error. To be freed by free().
|
||||
char *
|
||||
get_local_file_path(const char *name);
|
||||
|
||||
// returns true if the file exists and is not a directory
|
||||
bool
|
||||
is_regular_file(const char *path);
|
||||
|
@ -359,7 +359,7 @@ sc_v4l2_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||
|
||||
bool
|
||||
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);
|
||||
if (!vs->device_name) {
|
||||
LOGE("Could not strdup v4l2 device name");
|
||||
|
@ -18,7 +18,7 @@ struct sc_v4l2_sink {
|
||||
AVCodecContext *encoder_ctx;
|
||||
|
||||
char *device_name;
|
||||
struct sc_size frame_size;
|
||||
struct size frame_size;
|
||||
sc_tick buffering_time;
|
||||
|
||||
sc_thread thread;
|
||||
@ -34,7 +34,7 @@ struct sc_v4l2_sink {
|
||||
|
||||
bool
|
||||
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
|
||||
sc_v4l2_sink_destroy(struct sc_v4l2_sink *vs);
|
||||
|
@ -4,11 +4,11 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "cli.h"
|
||||
#include "options.h"
|
||||
#include "scrcpy.h"
|
||||
|
||||
static void test_flag_version(void) {
|
||||
struct scrcpy_cli_args args = {
|
||||
.opts = scrcpy_options_default,
|
||||
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||
.help = false,
|
||||
.version = false,
|
||||
};
|
||||
@ -23,7 +23,7 @@ static void test_flag_version(void) {
|
||||
|
||||
static void test_flag_help(void) {
|
||||
struct scrcpy_cli_args args = {
|
||||
.opts = scrcpy_options_default,
|
||||
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||
.help = false,
|
||||
.version = false,
|
||||
};
|
||||
@ -38,7 +38,7 @@ static void test_flag_help(void) {
|
||||
|
||||
static void test_options(void) {
|
||||
struct scrcpy_cli_args args = {
|
||||
.opts = scrcpy_options_default,
|
||||
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||
.help = false,
|
||||
.version = false,
|
||||
};
|
||||
@ -100,7 +100,7 @@ static void test_options(void) {
|
||||
|
||||
static void test_options2(void) {
|
||||
struct scrcpy_cli_args args = {
|
||||
.opts = scrcpy_options_default,
|
||||
.opts = SCRCPY_OPTIONS_DEFAULT,
|
||||
.help = false,
|
||||
.version = false,
|
||||
};
|
||||
|
@ -7,7 +7,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
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
|
||||
// in the individual module build.gradle files
|
||||
|
@ -2,7 +2,7 @@ apply plugin: 'checkstyle'
|
||||
check.dependsOn 'checkstyle'
|
||||
|
||||
checkstyle {
|
||||
toolVersion = '9.0.1'
|
||||
toolVersion = '6.19'
|
||||
}
|
||||
|
||||
task checkstyle(type: Checkstyle) {
|
||||
|
@ -37,14 +37,6 @@ page at http://checkstyle.sourceforge.net/config.html -->
|
||||
|
||||
<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">
|
||||
|
||||
<!-- Checks for Naming Conventions. -->
|
||||
@ -80,6 +72,13 @@ page at http://checkstyle.sourceforge.net/config.html -->
|
||||
|
||||
<!-- Checks for Size Violations. -->
|
||||
<!-- 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="ParameterNumber">
|
||||
<property name="ignoreOverriddenMethods" value="true"/>
|
||||
@ -153,6 +152,26 @@ page at http://checkstyle.sourceforge.net/config.html -->
|
||||
</module>
|
||||
<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>
|
||||
|
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
|
||||
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
|
||||
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 data/scrcpy-console.bat "$(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/avcodec-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 data/scrcpy-console.bat "$(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/avcodec-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
|
||||
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'
|
||||
|
||||
android {
|
||||
compileSdkVersion 31
|
||||
compileSdkVersion 30
|
||||
defaultConfig {
|
||||
applicationId "com.genymobile.scrcpy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 31
|
||||
targetSdkVersion 30
|
||||
versionCode 11900
|
||||
versionName "1.19"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
|
@ -2,6 +2,7 @@ package com.genymobile.scrcpy;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
|
Reference in New Issue
Block a user