Compare commits

..

18 Commits

Author SHA1 Message Date
f595d16cde wip 2021-10-21 10:17:03 +02:00
74fb32a584 Retrieve device serial for AOA
The serial is necessary to find the correct Android device for AOA.

If it is not explicitly provided by the user via -s, then execute "adb
getserialno" to retrieve it.
2021-10-21 09:59:24 +02:00
2f29b2e25d Expose function to get the device serial
Expose adb_get_serialno() to retrieve the device serial via the command
"adb getserialno".
2021-10-21 09:59:24 +02:00
710f7e1a7c Add read_pipe_all()
Add a convenience function to read from a pipe until all requested data
has been read.
2021-10-21 09:59:24 +02:00
f80c5107db Expose adb execution with redirection
Expose the redirection feature to the adb API.
2021-10-21 09:59:24 +02:00
0ed00c2e40 Add command execution with redirection
Expose command execution with pipes to stdin, stdout and stderr.

This will allow to read the result of adb commands.
2021-10-21 09:59:24 +02:00
874a4967a4 Support USB HID over AoAv2 mode for keyboard input
This provides a better input experience via
simulate physical keyboard, it converts
SDL_KeyboardEvent to proper HID events and send it
via HID over AoAv2.

This is a rewriting and bugfix of the origin code
from [@amosbird](https://github.com/amosbird).

Make sdl_keymod_to_hid_modifiers() more readable

Support MOD keys in HID mode

Enable Ctrl+V on HID mode

Support to send media events from hid_keyboard

Use existing --serial to replace --usb

Use explict option for input mode

Move HID keyboard setup code into functions

Send HID events in separated thread

Let libusb handle max package size

Fix HID keyboard report desc
2021-10-21 09:58:48 +02:00
a52779ae6b Extract mouse processor trait
Mainly for consistency with the keyboard processor trait.

This could allow to provide alternative mouse processors.
2021-10-21 09:58:48 +02:00
c8f65647f2 Extract keyboard processor trait
This will allow to provide alternative key processors.
2021-10-21 09:58:48 +02:00
b89c23b7f5 Fix trait header guards 2021-10-21 09:58:48 +02:00
7f3d2eded7 Fix "Could not found v4l2 muxer"
The AVOutputFormat name is a comma-separated list. In theory, possible
names for V4L2 are:

    - "video4linux2,v4l2"
    - "v4l2,video4linux2"
    - "v4l2"
    - "video4linux2"

To find the muxer in all cases, we must request exactly one muxer name
at a time.

PR #2718 <https://github.com/Genymobile/scrcpy/pull/2718>

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-10-21 09:58:48 +02:00
1d4e15f0d5 Simplify net_send_all()
There is no need to declare the variable before the loop.
2021-10-21 09:58:48 +02:00
ee55118282 Fix workarounds for Meizu
Workarounds.fillAppInfo() is necessary for Meizu devices even before the
first call to internalStreamScreen(), but it is harmful on other
devices (#940).

Therefore, simplify the workaround, by calling fillAppInfo() only if
Build.BRAND equals "meizu" (case insensitive).

Fixes #240 <https://github.com/Genymobile/scrcpy/issues/240> (again)
Fixes #2656 <https://github.com/Genymobile/scrcpy/issues/2656>
2021-10-21 09:58:48 +02:00
4c6096388e Add missing includes
Refs #2616 <https://github.com/Genymobile/scrcpy/issues/2616>
2021-10-21 09:58:48 +02:00
691bdb925f Add support for expandNotificationsPanel() variant
Some custom vendor ROM added an int as a parameter.

Fixes #2551 <https://github.com/Genymobile/scrcpy/issues/2551>
2021-10-21 09:58:48 +02:00
96e3963afc Update Readme.pt-br.md to v1.19
PR #2686 <https://github.com/Genymobile/scrcpy/pull/2686>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-10-08 21:46:56 +02:00
63b2b5ca4e Update italian translation to v1.19
PR #2650 <https://github.com/Genymobile/scrcpy/pull/2650>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-10-08 08:34:49 +02:00
a846c01883 Fix link in README 2021-09-11 21:36:21 +02:00
64 changed files with 1361 additions and 1619 deletions

View File

@ -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

View File

@ -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
View File

@ -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)

View File

@ -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.

View File

@ -1,7 +1,5 @@
# scrcpy (v1.19)
![icon](data/icon.png)
[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)

View File

@ -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

View File

@ -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', [

View File

@ -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).

View File

@ -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);
}

View File

@ -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

View File

@ -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},

View File

@ -5,7 +5,7 @@
#include <stdbool.h>
#include "options.h"
#include "scrcpy.h"
struct scrcpy_cli_args {
struct scrcpy_options opts;

View File

@ -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.
*

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View File

@ -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

View File

@ -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)

View File

@ -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");
}
}

View File

@ -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

View File

@ -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);
}

View File

@ -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
View File

@ -0,0 +1,53 @@
/* XPM */
static char * icon_xpm[] = {
"48 48 2 1",
" c None",
". c #96C13E",
" .. .. ",
" ... ... ",
" ... ...... ... ",
" ................ ",
" .............. ",
" ................ ",
" .................. ",
" .................... ",
" ..... ........ ..... ",
" ..... ........ ..... ",
" ...................... ",
" ........................ ",
" ........................ ",
" ........................ ",
" ",
" ",
" .... ........................ .... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" .... ........................ .... ",
" ........................ ",
" ...................... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" .... .... "};

View File

@ -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;

View File

@ -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"

View File

@ -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 {

View File

@ -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,
};

View File

@ -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),

View File

@ -6,6 +6,7 @@
#include <stdbool.h>
#include "controller.h"
#include "scrcpy.h"
#include "screen.h"
#include "trait/mouse_processor.h"

View File

@ -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,
};

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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");

View File

@ -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);

View File

@ -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, &params, &cbs, NULL)) {
return false;
}
// TODO SDL_Init(SDL_INIT_EVENTS) before starting server
if (!server_start(&s->server)) {
if (!server_start(&s->server, &params)) {
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

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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

View File

@ -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;

View File

@ -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
View 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
View 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

View File

@ -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__)

View File

@ -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
}

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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");

View File

@ -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);

View File

@ -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,
};

View File

@ -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

View File

@ -2,7 +2,7 @@ apply plugin: 'checkstyle'
check.dependsOn 'checkstyle'
checkstyle {
toolVersion = '9.0.1'
toolVersion = '6.19'
}
task checkstyle(type: Checkstyle) {

View File

@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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" "$@"

View File

@ -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"

View File

@ -2,6 +2,7 @@ package com.genymobile.scrcpy;
import android.view.KeyEvent;
import android.view.MotionEvent;
import org.junit.Assert;
import org.junit.Test;